Repository: asdf-vm/asdf Branch: master Commit: 9d5f08b100fc Files: 299 Total size: 1.1 MB Directory structure: gitextract_9qphyuig/ ├── .editorconfig ├── .git-blame-ignore-revs ├── .gitattributes ├── .github/ │ ├── CODEOWNERS │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.yaml │ │ ├── config.yml │ │ ├── documentation.yaml │ │ └── feature_request.yaml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dependabot.yml │ └── workflows/ │ ├── documentation.yml │ ├── lint.yml │ ├── release-build.yml │ ├── release.yml │ ├── semantic-pr.yml │ └── tests.yml ├── .gitignore ├── .release-please-manifest.json ├── .tool-versions ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── asdf.elv ├── asdf.fish ├── asdf.nu ├── asdf.ps1 ├── asdf.sh ├── ballad-of-asdf.md ├── bin/ │ └── private/ │ └── asdf-exec ├── defaults ├── docs/ │ ├── .gitattributes │ ├── .gitignore │ ├── .prettierignore │ ├── .tool-versions │ ├── .vitepress/ │ │ ├── config.ts │ │ ├── navbars.ts │ │ ├── sidebars.ts │ │ └── theme/ │ │ ├── custom.css │ │ └── index.ts │ ├── CNAME │ ├── contribute/ │ │ ├── core.md │ │ ├── documentation.md │ │ ├── first-party-plugins.md │ │ └── github-actions.md │ ├── guide/ │ │ ├── getting-started-legacy.md │ │ ├── getting-started.md │ │ ├── introduction.md │ │ └── upgrading-to-v0-16.md │ ├── index.md │ ├── ja-jp/ │ │ ├── contribute/ │ │ │ ├── core.md │ │ │ ├── documentation.md │ │ │ ├── first-party-plugins.md │ │ │ └── github-actions.md │ │ ├── guide/ │ │ │ ├── getting-started-legacy.md │ │ │ ├── getting-started.md │ │ │ ├── introduction.md │ │ │ ├── parts/ │ │ │ │ └── install-dependencies.md │ │ │ └── upgrading-to-v0-16.md │ │ ├── index.md │ │ ├── manage/ │ │ │ ├── commands.md │ │ │ ├── configuration.md │ │ │ ├── core.md │ │ │ ├── plugins.md │ │ │ └── versions.md │ │ ├── more/ │ │ │ ├── community-projects.md │ │ │ ├── faq.md │ │ │ └── thanks.md │ │ └── plugins/ │ │ └── create.md │ ├── ko-kr/ │ │ ├── contribute/ │ │ │ ├── core.md │ │ │ ├── documentation.md │ │ │ ├── first-party-plugins.md │ │ │ └── github-actions.md │ │ ├── guide/ │ │ │ ├── getting-started.md │ │ │ └── introduction.md │ │ ├── index.md │ │ ├── manage/ │ │ │ ├── commands.md │ │ │ ├── configuration.md │ │ │ ├── core.md │ │ │ ├── plugins.md │ │ │ └── versions.md │ │ ├── more/ │ │ │ ├── community-projects.md │ │ │ ├── faq.md │ │ │ └── thanks.md │ │ └── plugins/ │ │ └── create.md │ ├── manage/ │ │ ├── commands.md │ │ ├── configuration.md │ │ ├── core.md │ │ ├── dependencies.md │ │ ├── plugins.md │ │ └── versions.md │ ├── more/ │ │ ├── community-projects.md │ │ ├── faq.md │ │ └── thanks.md │ ├── package.json │ ├── parts/ │ │ ├── install-dependencies-cmds.md │ │ └── install-dependencies.md │ ├── plugins/ │ │ └── create.md │ ├── pt-br/ │ │ ├── contribute/ │ │ │ ├── core.md │ │ │ ├── documentation.md │ │ │ ├── first-party-plugins.md │ │ │ └── github-actions.md │ │ ├── guide/ │ │ │ ├── getting-started.md │ │ │ └── introduction.md │ │ ├── index.md │ │ ├── manage/ │ │ │ ├── commands.md │ │ │ ├── configuration.md │ │ │ ├── core.md │ │ │ ├── plugins.md │ │ │ └── versions.md │ │ ├── more/ │ │ │ ├── community-projects.md │ │ │ ├── faq.md │ │ │ └── thanks.md │ │ └── plugins/ │ │ └── create.md │ └── zh-hans/ │ ├── contribute/ │ │ ├── core.md │ │ ├── documentation.md │ │ ├── first-party-plugins.md │ │ └── github-actions.md │ ├── guide/ │ │ ├── getting-started-legacy.md │ │ ├── getting-started.md │ │ ├── introduction.md │ │ └── upgrading-to-v0-16.md │ ├── index.md │ ├── manage/ │ │ ├── commands.md │ │ ├── configuration.md │ │ ├── core.md │ │ ├── dependencies.md │ │ ├── plugins.md │ │ └── versions.md │ ├── more/ │ │ ├── community-projects.md │ │ ├── faq.md │ │ └── thanks.md │ ├── parts/ │ │ ├── install-dependencies-cmds.md │ │ └── install-dependencies.md │ └── plugins/ │ └── create.md ├── go.mod ├── go.sum ├── help.txt ├── internal/ │ ├── cli/ │ │ ├── cli.go │ │ └── set/ │ │ ├── set.go │ │ └── set_test.go │ ├── completions/ │ │ ├── asdf.bash │ │ ├── asdf.elvish │ │ ├── asdf.fish │ │ ├── asdf.nushell │ │ ├── asdf.zsh │ │ ├── completions.go │ │ └── completions_test.go │ ├── config/ │ │ ├── config.go │ │ ├── config_test.go │ │ └── testdata/ │ │ ├── asdfrc │ │ └── empty-asdfrc │ ├── data/ │ │ ├── data.go │ │ └── data_test.go │ ├── exec/ │ │ ├── exec.go │ │ ├── exec_test.go │ │ └── testdata/ │ │ └── script/ │ │ ├── exec-env.txtar │ │ └── exec-simple.txtar │ ├── execenv/ │ │ ├── execenv.go │ │ └── execenv_test.go │ ├── execute/ │ │ ├── execute.go │ │ ├── execute_test.go │ │ └── testdata/ │ │ └── script │ ├── git/ │ │ ├── git.go │ │ └── git_test.go │ ├── help/ │ │ ├── help.go │ │ ├── help.txt │ │ └── help_test.go │ ├── hook/ │ │ ├── hook.go │ │ ├── hook_test.go │ │ └── testdata/ │ │ └── asdfrc │ ├── info/ │ │ ├── info.go │ │ └── info_test.go │ ├── installs/ │ │ ├── installs.go │ │ └── installs_test.go │ ├── installtest/ │ │ └── installtest.go │ ├── paths/ │ │ ├── paths.go │ │ └── paths_test.go │ ├── pluginindex/ │ │ ├── pluginindex.go │ │ └── pluginindex_test.go │ ├── plugins/ │ │ ├── plugins.go │ │ └── plugins_test.go │ ├── repotest/ │ │ └── repotest.go │ ├── resolve/ │ │ ├── resolve.go │ │ ├── resolve_test.go │ │ └── testdata/ │ │ └── asdfrc │ ├── shims/ │ │ ├── shims.go │ │ ├── shims_test.go │ │ └── testdata/ │ │ └── asdfrc │ ├── toolversions/ │ │ ├── toolversions.go │ │ └── toolversions_test.go │ └── versions/ │ ├── testdata/ │ │ ├── asdfrc │ │ ├── list-all-elixir │ │ ├── list-all-python │ │ ├── list-all-ruby │ │ └── uninstall-asdfrc │ ├── versions.go │ └── versions_test.go ├── lib/ │ ├── commands/ │ │ ├── command-current.bash │ │ ├── command-env.bash │ │ ├── command-exec.bash │ │ ├── command-export-shell-version.bash │ │ ├── command-global.bash │ │ ├── command-help.bash │ │ ├── command-info.bash │ │ ├── command-install.bash │ │ ├── command-latest.bash │ │ ├── command-list-all.bash │ │ ├── command-list.bash │ │ ├── command-local.bash │ │ ├── command-plugin-add.bash │ │ ├── command-plugin-list-all.bash │ │ ├── command-plugin-list.bash │ │ ├── command-plugin-push.bash │ │ ├── command-plugin-remove.bash │ │ ├── command-plugin-test.bash │ │ ├── command-plugin-update.bash │ │ ├── command-reshim.bash │ │ ├── command-shim-versions.bash │ │ ├── command-uninstall.bash │ │ ├── command-update.bash │ │ ├── command-version.bash │ │ ├── command-where.bash │ │ ├── command-which.bash │ │ ├── reshim.bash │ │ └── version_commands.bash │ ├── functions/ │ │ ├── installs.bash │ │ ├── plugins.bash │ │ └── versions.bash │ └── utils.bash ├── release-please-config.json ├── scripts/ │ ├── checkstyle.py │ ├── install_dependencies.bash │ ├── lint.bash │ └── test.bash ├── staticcheck.conf ├── test/ │ ├── asdf_elvish.bats │ ├── asdf_fish.bats │ ├── asdf_nu.bats │ ├── asdf_pwsh.bats │ ├── asdf_sh.bats │ ├── banned_commands.bats │ ├── current_command.bats │ ├── fixtures/ │ │ ├── dummy_broken_plugin/ │ │ │ └── bin/ │ │ │ ├── download │ │ │ ├── install │ │ │ └── list-all │ │ ├── dummy_legacy_plugin/ │ │ │ └── bin/ │ │ │ ├── get-version-from-legacy-file │ │ │ ├── install │ │ │ ├── list-all │ │ │ ├── list-legacy-filenames │ │ │ └── parse-legacy-file │ │ ├── dummy_plugin/ │ │ │ ├── LICENSE │ │ │ └── bin/ │ │ │ ├── debug │ │ │ ├── download │ │ │ ├── get-version-from-legacy-file │ │ │ ├── help.overview │ │ │ ├── install │ │ │ ├── latest-stable │ │ │ ├── list-all │ │ │ ├── list-legacy-filenames │ │ │ ├── parse-legacy-file │ │ │ ├── post-plugin-add │ │ │ ├── post-plugin-update │ │ │ └── pre-plugin-remove │ │ ├── dummy_plugin_no_download/ │ │ │ ├── LICENSE │ │ │ └── bin/ │ │ │ └── install │ │ └── dummy_plugins_repo/ │ │ └── plugins/ │ │ ├── bar │ │ ├── dummy │ │ └── foo │ ├── get_asdf_config_value.bats │ ├── help_command.bats │ ├── info_command.bats │ ├── install_command.bats │ ├── latest_command.bats │ ├── list_command.bats │ ├── non_existent_command.bats │ ├── plugin_add_command.bats │ ├── plugin_extension_command.bats │ ├── plugin_list_all_command.bats │ ├── plugin_remove_command.bats │ ├── plugin_test_command.bats │ ├── plugin_update_command.bats │ ├── remove_command.bats │ ├── reshim_command.bats │ ├── setup_suite.bash │ ├── shim_env_command.bats │ ├── shim_exec.bats │ ├── shim_versions_command.bats │ ├── test_helpers.bash │ ├── uninstall_command.bats │ ├── update_command.bats │ ├── utils.bats │ ├── version_commands.bats │ ├── where_command.bats │ └── which_command.bats ├── tools.go └── version.txt ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.go] indent_style = tab [*.py] indent_size = 4 [*.fish] indent_size = 4 [*.md] trim_trailing_whitespace = false ================================================ FILE: .git-blame-ignore-revs ================================================ # Run shfmt on bash files b8dc5f160447baabab9c69683824512ded577254 # fix: Remove == inside [ (#1421) d81b81f9de2dc5961624464df04cef7cafae588c # chore: Fix ShellCheck errors in tests 407a6696c0739f5e368543c4fc1b14e41458f177 998180e3625643644603504bea75e3bcc668465f 6f64aa8d847d53f272d30bcf2532937c02826080 b5e981cf1d9a37af17e48f6a871ff692720df810 28b348a041b3dfd1898eb0073aa1e83bdae70e4a 720fd172004c9f29045eb2ad19f35eba47ae31c7 ffa018763c10de63e6548372e7eaad2ae53643d6 90ead5ea0ae06418c58a47f4c7732797af5fe336 ================================================ FILE: .gitattributes ================================================ ## GITATTRIBUTES # # Details per file setting: # text These files should be normalized (i.e. convert CRLF to LF). # binary These files are binary and should be left untouched. # # Note that binary is a macro for -text -diff. ###################################################################### ## AUTO-DETECT ## Handle line endings automatically for files detected as ## text and leave all files detected as binary untouched. ## This will handle all files NOT defined below. * text=auto ## SOURCE CODE *.bats text *.css text *.htm text *.html text *.js text *.sh text *.bash text *.fish text *.elv text #### asdf/bin/* explicitly define as text *asdf text *asdf-exec text *_asdf text #### asdf/test/fixtures/* should be covered by * text=auto on L14 ## DOCKER *.dockerignore text Dockerfile text ## DOCUMENTATION *.markdown text *.md text *.mdwn text *.mdown text *.mkd text *.mkdn text *.mdtxt text *.mdtext text *.txt text AUTHORS text CHANGELOG text CHANGES text CONTRIBUTING text COPYING text copyright text *COPYRIGHT* text INSTALL text license text LICENSE text NEWS text readme text *README* text TODO text ## CONFIGS .editorconfig text .gitattributes text .gitconfig text *.yaml text *.yml text ================================================ FILE: .github/CODEOWNERS ================================================ # Default owners for all the code in this repo * @asdf-vm/core ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.yaml ================================================ name: Bug Report description: Create a report to help us improve title: "bug: " labels: [bug] body: - type: markdown attributes: value: | Thanks for taking the time to fill out this bug report! - type: textarea id: description attributes: label: Describe the Bug description: A clear and concise description of what the bug is. validations: required: true - type: textarea id: reproduction attributes: label: Steps to Reproduce description: Tell us what actions you performed before the issue occurred placeholder: | 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error validations: required: true - type: textarea id: expected attributes: label: Expected Behaviour description: Tell us what should have happened? validations: required: true - type: textarea id: actual attributes: label: Actual Behaviour description: Tell us what happened instead validations: required: true - type: textarea id: environment attributes: label: Environment description: Copy the output of `asdf info` here render: shell validations: required: true - type: textarea id: plugins attributes: label: asdf plugins affected (if relevant) description: The plugins you list here should appear in the list from `asdf info` ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ # Force users to use one of our predefined issue templates blank_issues_enabled: false contact_links: - name: Question (StackOverflow) url: https://stackoverflow.com/questions/tagged/asdf-vm about: Ask new or browse existing questions on StackOverflow. ================================================ FILE: .github/ISSUE_TEMPLATE/documentation.yaml ================================================ name: Documentation description: Suggest a documentation improvements for this project labels: "documentation" body: - type: markdown attributes: value: | Thanks for taking the time to help improve our docs! - type: textarea id: problem attributes: label: How can we improve the documentation? description: Please provide a clear and concise description of the problem. The more information you can provide here, the better. placeholder: The documentation can be improved by... validations: required: true ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.yaml ================================================ name: Feature Request description: Suggest an idea for this project labels: "enhancement" body: - type: markdown attributes: value: | Thanks for taking the time to request this feature! - type: textarea id: problem attributes: label: Is your feature request related to a problem? Please describe description: Please provide a clear and concise description the problem this feature would solve. The more information you can provide here, the better. placeholder: I'm always frustrated when... validations: required: true - type: textarea id: solution attributes: label: Describe the proposed solution description: Please provide a clear and concise description of what you would like to happen. placeholder: I would like to see... validations: required: true - type: textarea id: alternatives attributes: label: "Describe similar `asdf` features and why they are not sufficient" description: "Describe asdf features you tried to use to accomplish what you wanted. Explain why they are insufficient for the task you were trying to accomplish." validations: required: true - type: textarea id: workarounds attributes: label: "Describe other workarounds you've considered" description: "A clear and concise description of any alternative solutions or features you've considered." validations: required: true ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ # Summary Fixes: List issue numbers here ## Other Information ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: # Maintain dependencies for GitHub Actions - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" reviewers: - "jthegedus" - "stratus3d" # Maintain dependencies for npm used in Documentation site - package-ecosystem: "npm" directory: "/docs" schedule: interval: "monthly" reviewers: - "jthegedus" groups: docs: patterns: - "*" ================================================ FILE: .github/workflows/documentation.yml ================================================ name: Documentation on: # trigger deployment on push to master branch when changes to docs/** push: paths: - "docs/**" branches: - master # trigger deployment manually workflow_dispatch: jobs: deploy: runs-on: ubuntu-latest defaults: run: working-directory: docs/ steps: - uses: actions/checkout@v6 with: # fetch all commits to get last updated time or other git log info fetch-depth: 0 - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: "20" - name: Cache dependencies uses: actions/cache@v5 id: npm-cache with: path: | **/node_modules key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.os }}-npm- - name: Install dependencies if: steps.npm-cache.outputs.cache-hit != 'true' run: npm install - name: Build VitePress site run: npm run build - name: Bundle CNAME with site dist run: cp CNAME .vitepress/dist - name: Deploy to GitHub Pages uses: crazy-max/ghaction-github-pages@v5 with: # deploy to gh-pages branch target_branch: gh-pages # deploy the default output dir of VitePress build_dir: docs/.vitepress/dist env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/lint.yml ================================================ name: Lint on: push: branches: - master pull_request: env: PYTHON_MIN_VERSION: "3.13.1" jobs: asdf-bash: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: asdf-vm/actions/install@v3 - uses: actions/setup-python@v6 with: python-version: ${{ env.PYTHON_MIN_VERSION }} - run: scripts/install_dependencies.bash - run: scripts/lint.bash --check asdf-golang: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Setup Go uses: actions/setup-go@v6 with: go-version: '1.24.9' - name: Install dependencies run: go get ./... - name: Run 'gofumpt' run: go run mvdan.cc/gofumpt -l -w . - name: Check format run: '[ -z "$(gofmt -l ./...)" ]' - name: Run 'revive' run: go run github.com/mgechev/revive -set_exit_status ./... - name: Vet run: go vet ./... - name: Lint run: go run honnef.co/go/tools/cmd/staticcheck -tests -show-ignored ./... - name: Build run: go build -v ./... actions: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - name: Check workflow files uses: docker://rhysd/actionlint:1.6.24 with: args: -color ================================================ FILE: .github/workflows/release-build.yml ================================================ name: Build Binaries for Release on: release: types: [published] workflow_dispatch: inputs: tag: description: 'Tag to build binaries for' required: true type: string permissions: contents: write packages: write jobs: build: name: Build release binaries runs-on: ubuntu-latest strategy: matrix: # windows isn't working on windows right now, add it to this list once # I fix the code. goos: [linux, darwin] goarch: ["386", amd64, arm64] exclude: - goarch: "386" goos: darwin #- goarch: arm64 # goos: windows steps: - name: Checkout uses: actions/checkout@v6 with: fetch-depth: 0 - name: Build Go binaries uses: wangyoucao577/go-release-action@v1 with: pre_command: "export CGO_ENABLED=0" github_token: ${{ secrets.GITHUB_TOKEN }} goos: ${{ matrix.goos }} goarch: ${{ matrix.goarch }} goversion: "1.24.9" binary_name: "asdf" project_path: ./cmd/asdf release_tag: ${{ github.event.release.tag_name || inputs.tag }} release_name: ${{ github.event.release.tag_name || inputs.tag }} ldflags: -s -X main.version=${{ github.event.release.tag_name || inputs.tag }} ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: push: branches: - master permissions: contents: write pull-requests: write jobs: release: runs-on: ubuntu-latest steps: - uses: googleapis/release-please-action@v4 name: create release with: token: ${{ secrets.ASDF_WORKFLOW_TOKEN }} config-file: release-please-config.json manifest-file: .release-please-manifest.json ================================================ FILE: .github/workflows/semantic-pr.yml ================================================ name: Lint on: pull_request_target: types: - opened - edited - synchronize jobs: semantic-pr: runs-on: ubuntu-latest steps: - uses: amannn/action-semantic-pull-request@v6.1.1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: scopes: | # The scope for all the Golang rewrite commits golang-rewrite # A list of all used scopes can be computed by running this command: # # git log --pretty=format:%s | rg '^[^: ]*\(([^):]*)\).*' -r '$1' | sort | uniq # # We only want to allow a limited set of scopes going forward, so # the list of valid scopes has been pared down here. docs website plugin completions deps ================================================ FILE: .github/workflows/tests.yml ================================================ name: Test on: push: branches: - master pull_request: jobs: detect-changes: runs-on: ubuntu-latest # Set job outputs to values from filter step outputs: documentation: ${{ steps.filter.outputs.documentation }} cli: ${{ steps.filter.outputs.cli }} go: ${{ steps.filter.outputs.go }} steps: - uses: actions/checkout@v6 with: fetch-depth: 0 - uses: dorny/paths-filter@v3 id: filter with: filters: | documentation: - '.github/workflows/**' - 'docs/**' cli: - '.github/workflows/**' - 'bin/**' - 'lib/**' - 'scripts/**' - 'test/**' - '.tool-versions' - 'asdf.*' - 'defaults' - 'help.txt' go: - '**.go' - 'go.mod' - 'go.sum' test-golang: needs: detect-changes if: ${{ needs.detect-changes.outputs.go == 'true' || needs.detect-changes.outputs.cli == 'true' }} strategy: matrix: os: [ubuntu-latest, macos-latest] runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v6 - name: Setup Go uses: actions/setup-go@v6 with: go-version: '1.24.9' - run: scripts/install_dependencies.bash - name: Install dependencies run: go get ./... - name: Run Go tests run: go test -coverprofile=/tmp/coverage.out -bench= -race ./... # Because I changed the test helper code Bash tests now fail. I removed them # from here to get passing checks. They can be added back at a later time if # I fix the test helper. documentation-site: needs: detect-changes # only run if # - changes to documentation # - pull_request (workflows/docs.yml deploys on main branch) if: ${{ github.event_name == 'pull_request' && needs.detect-changes.outputs.documentation == 'true' }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 with: # fetch all commits to get git log info for Vuepress fetch-depth: 0 # only run steps past here if changes to docs/** directory - uses: actions/setup-node@v6 with: node-version: "18" - uses: actions/cache@v5 id: npm-cache with: path: | docs/node_modules key: ${{ runner.os }}-npm-${{ hashFiles('docs/package-lock.json') }} restore-keys: | ${{ runner.os }}-npm- - name: Install dependencies if: steps.npm-cache.outputs.cache-hit != 'true' working-directory: docs/ run: npm install - name: Check errors by building Documentation site working-directory: docs/ run: npm run build ================================================ FILE: .gitignore ================================================ /installs /downloads /shims repository .vagrant keyrings /tmp dist/ # ignore build binary asdf ================================================ FILE: .release-please-manifest.json ================================================ { ".": "0.18.1" } ================================================ FILE: .tool-versions ================================================ golang 1.24.9 bats 1.8.2 shellcheck 0.10.0 shfmt 3.6.0 ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## [0.18.1](https://github.com/asdf-vm/asdf/compare/v0.18.0...v0.18.1) (2026-03-04) ### Bug Fixes * Only show already installed versions in shell completion for the asdf set command ([#2172](https://github.com/asdf-vm/asdf/pull/2172)) ([c3910d7](https://github.com/asdf-vm/asdf/commit/c3910d73776980e649605205352334c09c8c85b1)) * set LANG=C for locale-dependent tests ([#2197](https://github.com/asdf-vm/asdf/issues/2197)) ([2423f6d](https://github.com/asdf-vm/asdf/commit/2423f6da496b5b1955ce9cb19d524dc0a7b88e26)) ## [0.18.0](https://github.com/asdf-vm/asdf/compare/v0.17.0...v0.18.0) (2025-06-07) ### Features * `asdf list` exit with status code of 0 when no versions installed ([#2116](https://github.com/asdf-vm/asdf/issues/2116)) ([e7d5289](https://github.com/asdf-vm/asdf/commit/e7d5289c57894ebbc0f966cb91794efd970377af)) ### Bug Fixes * correct flag handling in commands run by `asdf exec` ([#2115](https://github.com/asdf-vm/asdf/issues/2115)) ([d6cd693](https://github.com/asdf-vm/asdf/commit/d6cd6930cff8e7159cb2f1a57b23bd0ec1faa6ac)) * only return version starting with number when no filter is supplied ([#2120](https://github.com/asdf-vm/asdf/issues/2120)) ([cf29b51](https://github.com/asdf-vm/asdf/commit/cf29b5136bbe481ae3803dbdb78086c808eeef7a)) * print all error output to stderr when shim can't be resolved ([#2109](https://github.com/asdf-vm/asdf/issues/2109)) ([c9049ea](https://github.com/asdf-vm/asdf/commit/c9049ea2fd09fc7958fb1a5a5b44e0670740465b)) * rename tool version filename environment variable for clarity ([#2101](https://github.com/asdf-vm/asdf/issues/2101)) ([e3d6014](https://github.com/asdf-vm/asdf/commit/e3d6014419296281c4156fc65a3e02bb542495a2)) * upgrade urfave/cli to version 3 ([#2105](https://github.com/asdf-vm/asdf/issues/2105)) ([392d09a](https://github.com/asdf-vm/asdf/commit/392d09a8b263a5ef18fd05f27312717bf9baa292)) ## [0.17.0](https://github.com/asdf-vm/asdf/compare/v0.16.7...v0.17.0) (2025-05-19) ### Features * **golang-rewrite:** add support for shim templates resolution ([#2076](https://github.com/asdf-vm/asdf/issues/2076)) ([a3bccea](https://github.com/asdf-vm/asdf/commit/a3bccea5c9b64bf81675efaa5c76d6eb367fd37f)) * log failure to add plugin in "plugin test" ([#2059](https://github.com/asdf-vm/asdf/issues/2059)) ([92de803](https://github.com/asdf-vm/asdf/commit/92de803ff15f1a887f031d570ae6404f008d829d)) * switch back to native git client ([#1998](https://github.com/asdf-vm/asdf/issues/1998)) ([1efa2bb](https://github.com/asdf-vm/asdf/commit/1efa2bbd04b833d0435a15bddd882beb973cfc2d)) ### Bug Fixes * address linter warning ([67581cf](https://github.com/asdf-vm/asdf/commit/67581cf030d4eb39f261acac5e861444fedad7f6)) * correct intersection logic in `shims.FindExecutable` function so ordering of multiple versions is preserved ([#2063](https://github.com/asdf-vm/asdf/issues/2063)) ([083f20a](https://github.com/asdf-vm/asdf/commit/083f20aa3e21cad594b35972ca570eb47e389899)) * correct output of install command when system or path version set ([#2097](https://github.com/asdf-vm/asdf/issues/2097)) ([82d67e3](https://github.com/asdf-vm/asdf/commit/82d67e3242b0ac4d01cccd9712daaae574ce6eca)) * ensures output always ends with a newline when printed ([#2098](https://github.com/asdf-vm/asdf/issues/2098)) ([6f4837e](https://github.com/asdf-vm/asdf/commit/6f4837ea9b82b442fdfa78c3bb315b088e60dc9c)) * handle tilde in env vars ([#2092](https://github.com/asdf-vm/asdf/issues/2092)) ([6da599a](https://github.com/asdf-vm/asdf/commit/6da599a93ad2655c2bf061c038da330ee4413985)) * remove default error action from plugin command ([#2027](https://github.com/asdf-vm/asdf/issues/2027)) ([c376481](https://github.com/asdf-vm/asdf/commit/c376481cb4d1fa1e67dd9ef326381c07c935151d)) * remove unused ForcePrepend option from Go code ([#2089](https://github.com/asdf-vm/asdf/issues/2089)) ([49e9f33](https://github.com/asdf-vm/asdf/commit/49e9f330a719de6bd599b84c90b93e7d7358043c)) * set correct version for go install and make builds ([#2077](https://github.com/asdf-vm/asdf/issues/2077)) ([4c73527](https://github.com/asdf-vm/asdf/commit/4c73527d6323ca41d8ea9a9e78a8db49f3794d16)) ## [0.16.7](https://github.com/asdf-vm/asdf/compare/v0.16.6...v0.16.7) (2025-03-25) ### Bug Fixes * remove comment from first line zsh completion output ([#2035](https://github.com/asdf-vm/asdf/issues/2035)) ([#2037](https://github.com/asdf-vm/asdf/issues/2037)) ([74d7b17](https://github.com/asdf-vm/asdf/commit/74d7b17a1cc9f640cf0f5134416d1cf7a56fe19f)) ## [0.16.6](https://github.com/asdf-vm/asdf/compare/v0.16.5...v0.16.6) (2025-03-21) ### Bug Fixes * correct ASDF_INSTALL_* envvar unset test ([#2006](https://github.com/asdf-vm/asdf/issues/2006)) ([6fbf94a](https://github.com/asdf-vm/asdf/commit/6fbf94a75b8e045eea53038182e376b21a6947e4)) * correct concurrency to align with documentation ([#2014](https://github.com/asdf-vm/asdf/issues/2014)) ([807ea38](https://github.com/asdf-vm/asdf/commit/807ea3883139da48300e72931680431aa35e593d)) * correct handling of `ASDF_FORCE_PREPEND` environment variable ([#2011](https://github.com/asdf-vm/asdf/issues/2011)) ([43a84a0](https://github.com/asdf-vm/asdf/commit/43a84a024faeacb04044c9e2cf20ccbe87ea4263)) * improve zsh completion suggestions ([#2022](https://github.com/asdf-vm/asdf/issues/2022)) ([b1cf58d](https://github.com/asdf-vm/asdf/commit/b1cf58d2bd01c1c2c3662cca6bd8927d68a37258)) * remove filtering from latest-stable call ([#2032](https://github.com/asdf-vm/asdf/issues/2032)) ([6fcdcdf](https://github.com/asdf-vm/asdf/commit/6fcdcdf6df693fec6d643fab54e2d520bd5b539b)) * remove install directory for version when install fails ([#2024](https://github.com/asdf-vm/asdf/issues/2024)) ([932ac46](https://github.com/asdf-vm/asdf/commit/932ac468b7c24c2adef90a293a1f7280a0074cc4)) ## [0.16.5](https://github.com/asdf-vm/asdf/compare/v0.16.4...v0.16.5) (2025-03-04) ### Bug Fixes * always propagate env variables when executing commands ([#1982](https://github.com/asdf-vm/asdf/issues/1982)) ([80265a8](https://github.com/asdf-vm/asdf/commit/80265a8eecedc623cb8cf5cca18ae563e9d4f94c)) * build static binary to improve portability ([#1993](https://github.com/asdf-vm/asdf/issues/1993)) ([45047a6](https://github.com/asdf-vm/asdf/commit/45047a6c451599e718f996fdadbdcea3ecf683fd)) * correct exit status when sub-command does not exist ([#1991](https://github.com/asdf-vm/asdf/issues/1991)) ([3dd0dd3](https://github.com/asdf-vm/asdf/commit/3dd0dd3b475d1c4ddcb6d76248a988be5cceef51)), closes [#1928](https://github.com/asdf-vm/asdf/issues/1928) * latest version returns latest version ([#1996](https://github.com/asdf-vm/asdf/issues/1996)) ([0ceac7a](https://github.com/asdf-vm/asdf/commit/0ceac7af8c126980901caba4d8daa80900819451)) * preserve files untracked by Git on plugin update ([#1995](https://github.com/asdf-vm/asdf/issues/1995)) ([d4d8db0](https://github.com/asdf-vm/asdf/commit/d4d8db035d9f349bfed513af6976734db18e2c14)) * set correct env vars on recursive calls ([#1989](https://github.com/asdf-vm/asdf/issues/1989)) ([97a91cc](https://github.com/asdf-vm/asdf/commit/97a91cc8d01bda0896a50dff50a162e87fd61e57)) * simplify env vars parsing ([#1988](https://github.com/asdf-vm/asdf/issues/1988)) ([8990b6b](https://github.com/asdf-vm/asdf/commit/8990b6b4ae3c9754f3764289f0d7cf410815d29d)), closes [#1986](https://github.com/asdf-vm/asdf/issues/1986) ## [0.16.4](https://github.com/asdf-vm/asdf/compare/v0.16.3...v0.16.4) (2025-02-19) ### Bug Fixes * Add a newline delimiter when suggesting versions to install ([#1972](https://github.com/asdf-vm/asdf/issues/1972)) ([38bea71](https://github.com/asdf-vm/asdf/commit/38bea7145495a53c1a6fbad0542a32a4e7937e91)) * correct version resolution order to restore legacy file fallback behavior ([#1956](https://github.com/asdf-vm/asdf/issues/1956)) ([6696d47](https://github.com/asdf-vm/asdf/commit/6696d4702937442842a3643fab31d21a7fd0208f)) * support environment variables with equals sign and newlines in value ([#1977](https://github.com/asdf-vm/asdf/issues/1977)) ([1acf082](https://github.com/asdf-vm/asdf/commit/1acf0824ccfd33f118cb7440970df9e43899a1c1)) ## [0.16.3](https://github.com/asdf-vm/asdf/compare/v0.16.2...v0.16.3) (2025-02-17) ### Bug Fixes * add missing version command ([#1931](https://github.com/asdf-vm/asdf/issues/1931)) ([5339c41](https://github.com/asdf-vm/asdf/commit/5339c413d2fd77e971ed9b7621f0454b96fe3a0d)) * correct formatting of version in "already installed" error message ([df5e283](https://github.com/asdf-vm/asdf/commit/df5e283fb74a63faecd9ab234af1f0c24f1afdcd)) * correct typo in `Upgrading to 0.16.0` documentation ([#1938](https://github.com/asdf-vm/asdf/issues/1938)) ([7e8e5f6](https://github.com/asdf-vm/asdf/commit/7e8e5f60d13b0672e65982e21d7dc864246be8eb)) * don't error if version already installed ([06f8990](https://github.com/asdf-vm/asdf/commit/06f89907b2002db0e53b9bb2acd8ad11935f051c)) * pass environment variables through to `exec-env` callback ([9e6b559](https://github.com/asdf-vm/asdf/commit/9e6b5594080acd4208427505d9018123f1fb1f36)) * repair invalid fish shell completion code ([#1936](https://github.com/asdf-vm/asdf/issues/1936)) ([8388f99](https://github.com/asdf-vm/asdf/commit/8388f992e9be7f21313d8c8e363b43e82e44f207)) * return no error from shims.RemoveAll when shims dir missing ([#1967](https://github.com/asdf-vm/asdf/issues/1967)) ([45c31c9](https://github.com/asdf-vm/asdf/commit/45c31c9761f62a45896f6e45707a60fd0c1f4111)) ## [0.16.2](https://github.com/asdf-vm/asdf/compare/v0.16.1...v0.16.2) (2025-02-08) ### Bug Fixes * correct Bash completion ([#1886](https://github.com/asdf-vm/asdf/issues/1886)) ([fdb1bc7](https://github.com/asdf-vm/asdf/commit/fdb1bc793a06263a0eac8818c14498e23906108a)) * improve completions for Zsh and Fish ([#1912](https://github.com/asdf-vm/asdf/issues/1912)) ([2f806de](https://github.com/asdf-vm/asdf/commit/2f806de830655d9146d25663d74e3fceedcc300f)) * correct help for asdf set command ([#1920](https://github.com/asdf-vm/asdf/issues/1920)) ([554b4ea](https://github.com/asdf-vm/asdf/commit/554b4eaf351c08fed2261715308320838f0c5afb)) * correct help for asdf set command in Bash completion ([#1921](https://github.com/asdf-vm/asdf/issues/1921)) ([63e7dca](https://github.com/asdf-vm/asdf/commit/63e7dcaeae46d6d43ae63db9bf635e227a6ba944)) * improve Zsh completion suggestions ([#1925](https://github.com/asdf-vm/asdf/issues/1925)) ([e190624](https://github.com/asdf-vm/asdf/commit/e190624fa82fb2caf4d56521232de4e873b63118)) * run go tests when go.mod or go.sum change ([#1917](https://github.com/asdf-vm/asdf/issues/1917)) ([6e4a7b5](https://github.com/asdf-vm/asdf/commit/6e4a7b5ad3d6abdbb98faa383bc41643b35ef6bf)) * fix typo ([#1897](https://github.com/asdf-vm/asdf/issues/1897)) ([98ffa86](https://github.com/asdf-vm/asdf/commit/98ffa861e9b64a2e029a0ed26205bfcc81838180)) * update make utils script to set correct version ([892736b](https://github.com/asdf-vm/asdf/commit/892736bf76ac37487cfca75d2cfcc57d5cdec913)) * update urfave/cli to fix cmd output ([#1914](https://github.com/asdf-vm/asdf/issues/1914)) ([3525e9e](https://github.com/asdf-vm/asdf/commit/3525e9ed4edb05f15a15f00378f5336ef29aa2f4)) ## [0.16.1](https://github.com/asdf-vm/asdf/compare/v0.16.0...v0.16.1) (2025-02-05) ### Bug Fixes * correct SliceToMap environment variable parsing function ([#1879](https://github.com/asdf-vm/asdf/issues/1879)) ([e63aec6](https://github.com/asdf-vm/asdf/commit/e63aec61020a907fe5960d74b6d3dbf229214ae0)) * remove old hyphenated command from help ([1f0296a](https://github.com/asdf-vm/asdf/commit/1f0296a3d11a9df0d300ca61f59d097750ab6f1c)) * replace reference to removed subcommands ([#1868](https://github.com/asdf-vm/asdf/issues/1868)) ([0785d35](https://github.com/asdf-vm/asdf/commit/0785d35263b2bd5ad570ee6706e1b38d8dd05422)) * revert change to old Bash help text ([de019fd](https://github.com/asdf-vm/asdf/commit/de019fde849d2f3a258b3f40e5b2004d1973c804)) * use version in home dir when no version found in root dir ([#1883](https://github.com/asdf-vm/asdf/issues/1883)) ([5ae5f76](https://github.com/asdf-vm/asdf/commit/5ae5f769f1042add6219e98633063e76a03e12b9)) ## [0.16.0](https://github.com/asdf-vm/asdf/compare/v0.15.0...v0.16.0) (2025-01-30) ### Features * **Rewrite asdf in Golang** The rewrite in Go was spread across 88 pull requests that are all included in this release. The primary goal of the rewrite was to create a codebase that was faster, simpler and easier to maintain. The rewrite tries to maintain feature parity with the previous version. However, a number of breaking changes were introduced. Some of these were due to the change of language, a few out of a desire to simplify the code, and some to improve the user experience. For the full list of breaking changes and the upgrade guide visit the [Upgrading to 0.16.0 page](https://asdf-vm.com/guide/upgrading-to-v0-16.html#breaking-changes) on the asdf website. **It is highly recommended that you read this guide before upgrading**. A warning has also been added to the Bash code for asdf in 0.16.0. anyone trying to use asdf as they did in version 0.15.0 and earlier will get a warning message instructing them to follow the upgrade guide. The full list of pull requests and merge commits for this rewrite are listed below. ([3a9f539](https://github.com/asdf-vm/asdf/commit/3a9f539aa092fa7c043661232f8caa154e861c6f)) ([f41ce90](https://github.com/asdf-vm/asdf/commit/f41ce90dc4410da7bb76c5ccf73147759716be07)) ([#1833](https://github.com/asdf-vm/asdf/issues/1833)) ([4f9a5d3](https://github.com/asdf-vm/asdf/commit/4f9a5d3d314bc6a8abd5e14d9c38472055033fa7)) ([d06d71f](https://github.com/asdf-vm/asdf/commit/d06d71f9f6b2fd6069c6fbf27bc7b13b8b0ec5c2)) ([7d5281a](https://github.com/asdf-vm/asdf/commit/7d5281a8a9151d57113ea0dd5d2fea2b07f55228)) ([8ad3472](https://github.com/asdf-vm/asdf/commit/8ad3472abc64d8a07589a16096142b52aeae18af)) ([b40beb6](https://github.com/asdf-vm/asdf/commit/b40beb6039b031d350bd1c621f34d90f23f72765)) ([b23e5a3](https://github.com/asdf-vm/asdf/commit/b23e5a320fd231c4fa55baa3b32d90b55a6ff4f1)) ([bc05110](https://github.com/asdf-vm/asdf/commit/bc0511015920acd8421bbccaead86e2badf0c2ae)) ([477e9d5](https://github.com/asdf-vm/asdf/commit/477e9d57293b8c99bcb184cfc72e064f4b68eda0)) ([6d708b2](https://github.com/asdf-vm/asdf/commit/6d708b280720d2144ae7976229fd5630eeb31eaf)) ([572ed07](https://github.com/asdf-vm/asdf/commit/572ed07f2bdb31f04a7244d1d127594ef9922db0)) ([19a0597](https://github.com/asdf-vm/asdf/commit/19a0597502ebecb0dfe2946fe19d9f9da3d57a18)) ([b33ab64](https://github.com/asdf-vm/asdf/commit/b33ab6463c7825a0d03f82f430cfad18a1e941c8)) ([b966ca6](https://github.com/asdf-vm/asdf/commit/b966ca66271f1be81abee751dec929f97cadfab4)) ([8db188a](https://github.com/asdf-vm/asdf/commit/8db188a702b1a64812fbb6e5832ec74ed47dfb34)) ([3fd4a83](https://github.com/asdf-vm/asdf/commit/3fd4a839757a2646054f2d86f487731e0715eeaa)) ([09d06ff](https://github.com/asdf-vm/asdf/commit/09d06ff125107c19a24043e52cf60b378b8b4c3b)) ([d2afb85](https://github.com/asdf-vm/asdf/commit/d2afb85eb80bce85e79c9c0d91ad3103a7f985f0)) ([778ab34](https://github.com/asdf-vm/asdf/commit/778ab34a6f47ac913a0ebc5035d74bfbb3744ebe)) ([9f09f78](https://github.com/asdf-vm/asdf/commit/9f09f78ec06b28bd90d7cdd42762acc1e18011c7)) ([6568891](https://github.com/asdf-vm/asdf/commit/65688915a5d6816b15acf7332d22920d10e8d99a)) ([771f184](https://github.com/asdf-vm/asdf/commit/771f18493fcc8684f02373b2f03750937016f51a)) ([8313ebc](https://github.com/asdf-vm/asdf/commit/8313ebca2d2305b8e176286ae0a43b0359d78059)) ([be52d8f](https://github.com/asdf-vm/asdf/commit/be52d8f39c3aa253496c5469963cd0ecc995d19c)) ([c2e5ee6](https://github.com/asdf-vm/asdf/commit/c2e5ee652532f54aaed7e93796f7239a2a333446)) ([9097696](https://github.com/asdf-vm/asdf/commit/9097696a4fa5ad7efeb383509ecce16125df3cbf)) ([ad0907a](https://github.com/asdf-vm/asdf/commit/ad0907a74df1edf42e7c72ecba96ea52e3de2bcf)) ([2b02f51](https://github.com/asdf-vm/asdf/commit/2b02f51fa1acb2090713f6dfa70903281d28735a)) ([c480044](https://github.com/asdf-vm/asdf/commit/c4800443bd805afd1878891c56108366f3faba0c)) ([26b91aa](https://github.com/asdf-vm/asdf/commit/26b91aa8288787e552dbc50a1c2234ad2264205c)) ([202cdae](https://github.com/asdf-vm/asdf/commit/202cdae831b4909280e0f19ff77bbe4acaec36b4)) ([325cd33](https://github.com/asdf-vm/asdf/commit/325cd3334b3898cbd084ac3b3686458cc92b613e)) ([07b5813](https://github.com/asdf-vm/asdf/commit/07b5813566431ce9f6245e884fe86dd13ad1c195)) ([822e14c](https://github.com/asdf-vm/asdf/commit/822e14c561ab1df74f091bb46bcb340ae9c2bda6)) ([53cd454](https://github.com/asdf-vm/asdf/commit/53cd4544741cab2e58ca7ea8aa36c9c28890b0a1)) ([9f6a65f](https://github.com/asdf-vm/asdf/commit/9f6a65f5dda41d25a9e894ff6a8482810fd6dec8)) ([bd7ab9a](https://github.com/asdf-vm/asdf/commit/bd7ab9ae0844c4c9e41760661a555bab13509752)) ([b6ec89f](https://github.com/asdf-vm/asdf/commit/b6ec89f95f6afedbbdb2daaf4c3d2f67f50f0cf1)) ([162cb8e](https://github.com/asdf-vm/asdf/commit/162cb8eceed53f58c20c547d29ca8068e411ce64)) ([d94bace](https://github.com/asdf-vm/asdf/commit/d94baceb184b8a501a8eb1fd2cd33ca75bf962d9)) ([e7df5ff](https://github.com/asdf-vm/asdf/commit/e7df5ff3253b6caba8ceef6b0754d17fcb765a9b)) ([369beeb](https://github.com/asdf-vm/asdf/commit/369beebab9c80197eb877660add870c0a6be5bda)) ([#1829](https://github.com/asdf-vm/asdf/issues/1829)) ([f68b29b](https://github.com/asdf-vm/asdf/commit/f68b29bd508cfe993d4926377891d0a3e902f2ef)) ([26a3815](https://github.com/asdf-vm/asdf/commit/26a38159483588cd0143b58272f42c96e127d265)) ([ccc98ad](https://github.com/asdf-vm/asdf/commit/ccc98ad4e997b73dcc1e9d6839a17a539c5ec649)) ([447acd1](https://github.com/asdf-vm/asdf/commit/447acd13d1225e2e9ef80ae853c5da02fca034c3)) ([72c20b1](https://github.com/asdf-vm/asdf/commit/72c20b1b51d223dd6633f279258cb8bc54eb4661)) ([924eecf](https://github.com/asdf-vm/asdf/commit/924eecfa6af25f59162e3f35b913a1d0247bd34f)) ([f5a5967](https://github.com/asdf-vm/asdf/commit/f5a59677df42f990529bd8a1668fcfea353ea4a0)) ([3af0291](https://github.com/asdf-vm/asdf/commit/3af02913169abf9049933abc4d99406687d743c3)) ([9ed4216](https://github.com/asdf-vm/asdf/commit/9ed4216525a2076b84e9e5fc6d454746a3b4d825)) ([b9e79e6](https://github.com/asdf-vm/asdf/commit/b9e79e64564ad5a94c9d435458e1a57053744b8d)) ([626bde0](https://github.com/asdf-vm/asdf/commit/626bde0a9785e400eaeb2fa72fb11fff6102b458)) ([f639f8a](https://github.com/asdf-vm/asdf/commit/f639f8a4d0a3fcc3cc175def3b3b2d1ebdac1ade)) ([c0963a3](https://github.com/asdf-vm/asdf/commit/c0963a38a62e61586ef0dd34ea568d23345e3739)) ([cb49b64](https://github.com/asdf-vm/asdf/commit/cb49b64a5adb8944acff0c03e617df0a0f39453c)) ([f74efbf](https://github.com/asdf-vm/asdf/commit/f74efbf1bff5d7489f86f2f568eba2667d95645e)) ([15e1f06](https://github.com/asdf-vm/asdf/commit/15e1f06f3751c9b72deea5cbad587140ba03c645)) ([620c0d8](https://github.com/asdf-vm/asdf/commit/620c0d87e8bf1e1c99a5772c9103fa017e75f487)) ([518a0fa](https://github.com/asdf-vm/asdf/commit/518a0fa4422cff1625cb7b676f40c4bc0a42eed9)) ([5d5d04f](https://github.com/asdf-vm/asdf/commit/5d5d04fbb7a41c83ae5031182d7d671fae434cb5)) ([2fc8006](https://github.com/asdf-vm/asdf/commit/2fc80064902a3b467a82466add1db186a99f075a)) ([c859384](https://github.com/asdf-vm/asdf/commit/c8593842eeb2a3eae74af7747afc62fa406a5b54)) ([2951011](https://github.com/asdf-vm/asdf/commit/2951011090a2a8ac3f42d54198a2447213171c38)) ([c5092c6](https://github.com/asdf-vm/asdf/commit/c5092c6dbfce3614f3de2db08afaa6fcb8b7792e)) ([3f9744d](https://github.com/asdf-vm/asdf/commit/3f9744df0fd8b23684ef2c705642232c5d2151af)) ([985c181](https://github.com/asdf-vm/asdf/commit/985c181118b39bb555a2bd2a1cdbf111a25bd512)) ([18e21c9](https://github.com/asdf-vm/asdf/commit/18e21c90284be35fa01e957b49336ffac515e19b)) ([0058988](https://github.com/asdf-vm/asdf/commit/005898800bded840fa7dd9b62a0c60a93c124c8c)) ([#1820](https://github.com/asdf-vm/asdf/issues/1820)) ([c3bd8fe](https://github.com/asdf-vm/asdf/commit/c3bd8feccd2e4589bc905ccf6b1bcd8dc74f4f37)) ([8394e85](https://github.com/asdf-vm/asdf/commit/8394e858fee419ed38833fa953b122fe04754830)) ([5266ba5](https://github.com/asdf-vm/asdf/commit/5266ba581ddf5f96de2d20391e340bb4f7c123c4)) ([3155dc3](https://github.com/asdf-vm/asdf/commit/3155dc374e9cbea6d2d792fef08914124320a292)) ([7dfa8b4](https://github.com/asdf-vm/asdf/commit/7dfa8b40ae5e557b90fb7917bc86a569cf2bd0a6)) ([5a24864](https://github.com/asdf-vm/asdf/commit/5a2486463238e2473aab739520793011267be19f)) ([80ac9bb](https://github.com/asdf-vm/asdf/commit/80ac9bb51c684c04829328c28a06aa1f1f67a8f2)) ([#1849](https://github.com/asdf-vm/asdf/issues/1849)) ([8b1b024](https://github.com/asdf-vm/asdf/commit/8b1b024f51b166bf29481162fa57e5752d0d9300)) ([1b3c426](https://github.com/asdf-vm/asdf/commit/1b3c42699a30b6bcc7295d3f433dad9cd520f130)) ([163d6b4](https://github.com/asdf-vm/asdf/commit/163d6b4b462954a2f52b6a1b9e09faa806292260)) ([87d3c06](https://github.com/asdf-vm/asdf/commit/87d3c06cf5dc859dab4d1af3dec290ed49651b67)) ([3f17a80](https://github.com/asdf-vm/asdf/commit/3f17a80fbe34ee68a3d5580798374caf1a7bc4ae)) ([#1841](https://github.com/asdf-vm/asdf/issues/1841)) ([251812b](https://github.com/asdf-vm/asdf/commit/251812bfd58a768c4a51b82c0f5f88601abfd84c)) ([#1852](https://github.com/asdf-vm/asdf/issues/1852)) ([78a00fc](https://github.com/asdf-vm/asdf/commit/78a00fc90345bd77d83bb9b2b61f85756fceb1a6)) ([6b45a5e](https://github.com/asdf-vm/asdf/commit/6b45a5e5f74f60a22fd20b331163f5e5d6a3881c)) ([2a31caf](https://github.com/asdf-vm/asdf/commit/2a31cafd38128be86696c0a46e0223a95b8129fe)) ([7439ea9](https://github.com/asdf-vm/asdf/commit/7439ea916829f7c3f1f2932fa17b3d2848c2dbe7)) ([88af4ee](https://github.com/asdf-vm/asdf/commit/88af4eea00c395407a4b904e80e307ad864a6535)) ### Bug Fixes * bash command set end of line LF ([#1847](https://github.com/asdf-vm/asdf/issues/1847)) ([8211421](https://github.com/asdf-vm/asdf/commit/8211421f4e0d1f62d2d2b7436f9070040231fcbd)) ## [0.15.0](https://github.com/asdf-vm/asdf/compare/v0.14.1...v0.15.0) (2024-12-18) ### Features * golang-rewrite: remove `asdf update` command to prepare for Go version ([#1806](https://github.com/asdf-vm/asdf/issues/1806)) ([15571a2](https://github.com/asdf-vm/asdf/commit/15571a2d28818644673bbaf0fcf7d1d9e342cda4)) ### Patches * completions: Address two Bash completion bugs ([#1770](https://github.com/asdf-vm/asdf/issues/1770)) ([ebdb229](https://github.com/asdf-vm/asdf/commit/ebdb229ce68979a18dae5c0922620b860c56b22f)) * make plugin-test work on alpine linux ([#1778](https://github.com/asdf-vm/asdf/issues/1778)) ([f5a1f3a](https://github.com/asdf-vm/asdf/commit/f5a1f3a0a8bb50796f6ccf618d2bf4cf3bdea097)) * nushell: nushell spread operator ([#1777](https://github.com/asdf-vm/asdf/issues/1777)) ([a0ce37b](https://github.com/asdf-vm/asdf/commit/a0ce37b89bd5eb4ddaa806f96305ee99a8c5d365)) * nushell: Use correct env var for shims dir ([#1742](https://github.com/asdf-vm/asdf/issues/1742)) ([2f07629](https://github.com/asdf-vm/asdf/commit/2f0762991c35da933b81ba6ab75457a504deedbb)) * when download path got removed, it should use -f to force delete the download files ([#1746](https://github.com/asdf-vm/asdf/issues/1746)) ([221507f](https://github.com/asdf-vm/asdf/commit/221507f1c0288f0df13315a7f0f2c0a7bc39e7c2)) ### Documentation * add Korean translation ([#1757](https://github.com/asdf-vm/asdf/issues/1757)) ([9e16306](https://github.com/asdf-vm/asdf/commit/9e16306f42b4bbffd62779aaebb9cbbc9ba59007)) * propose edits for tiny typographical/grammatical errors ([#1747](https://github.com/asdf-vm/asdf/issues/1747)) ([d462b55](https://github.com/asdf-vm/asdf/commit/d462b55ec9868eeaddba4b70850aba908236dd93)) * split Lint and Test badges for title asdf in `README.MD` ([#1725](https://github.com/asdf-vm/asdf/issues/1725)) ([c778ea1](https://github.com/asdf-vm/asdf/commit/c778ea1deca19d8ccd91253c2f206a6b51a0a9b1)) * Update Japanese(ja-jp) Translations ([#1715](https://github.com/asdf-vm/asdf/issues/1715)) ([bd19e4c](https://github.com/asdf-vm/asdf/commit/bd19e4cbdc2f0a9380dbdfcec46584d619e8ed56)) ## [0.14.1](https://github.com/asdf-vm/asdf/compare/v0.14.0...v0.14.1) (2024-08-15) ### Patches * Only display the "can't keep downloads" warning when asked to keep downloads ([#1756](https://github.com/asdf-vm/asdf/issues/1756)) ([44f3efb](https://github.com/asdf-vm/asdf/commit/44f3efb63b7517520f4610d504d30783a85c9d79)) ## [0.14.0](https://github.com/asdf-vm/asdf/compare/v0.13.1...v0.14.0) (2024-01-19) ### ⚠ BREAKING CHANGES * Enable `pipefail` ([#1608](https://github.com/asdf-vm/asdf/issues/1608)) ### Patches * `plugin test` git-ref to use plugin repo default branch ([#1694](https://github.com/asdf-vm/asdf/issues/1694)) ([6d8cf9d](https://github.com/asdf-vm/asdf/commit/6d8cf9d44b3d985ac59f1eac827c5275392f90fd)) * avoid mention of `ASDF_NU_DIR` ([#1660](https://github.com/asdf-vm/asdf/issues/1660)) ([dfea89c](https://github.com/asdf-vm/asdf/commit/dfea89ccc703f3ef5a87c4b85726456d70167d89)) * Enable `pipefail` ([#1608](https://github.com/asdf-vm/asdf/issues/1608)) ([4085e55](https://github.com/asdf-vm/asdf/commit/4085e5542bac824ea124610ad247c2f90d1b8d93)) * **fish:** use PATH instead of fish_user_paths ([#1709](https://github.com/asdf-vm/asdf/issues/1709)) ([5327697](https://github.com/asdf-vm/asdf/commit/53276973f7c99695cd9a28b04c010b006d7f60ca)) * list `asdf version` command under help.txt UTILS section ([#1673](https://github.com/asdf-vm/asdf/issues/1673)) ([240a5fb](https://github.com/asdf-vm/asdf/commit/240a5fbdea1de055672d02f83db1de990ea2bf83)) * **nushell:** Use `def --env` instead of `def-env` ([#1681](https://github.com/asdf-vm/asdf/issues/1681)) ([3b8f400](https://github.com/asdf-vm/asdf/commit/3b8f400c3e628851286bfebd8da5bc7ab45cd676)) * plugin extension commands to not require `bin/` directory ([#1643](https://github.com/asdf-vm/asdf/issues/1643)) ([61420ad](https://github.com/asdf-vm/asdf/commit/61420ad90829b2c9bf1ca16681a2eb652adcc755)) * use universal scope for fish_user_paths ([#1699](https://github.com/asdf-vm/asdf/issues/1699)) ([0ffee72](https://github.com/asdf-vm/asdf/commit/0ffee7224bc00a917ceaea689c6268fd1f03bd62)) * warn if plugin does not support keeping downloads if configured ([#1644](https://github.com/asdf-vm/asdf/issues/1644)) ([19515ed](https://github.com/asdf-vm/asdf/commit/19515eda3b91167b0d76c35ffc4402de688007e0)) ### Documentation * add Japanese translation ([#1667](https://github.com/asdf-vm/asdf/issues/1667)) ([2b9bec7](https://github.com/asdf-vm/asdf/commit/2b9bec7710cd18e51a01652e1f58cc309baf2fd7)) * fix some pt-br spelling ([#1640](https://github.com/asdf-vm/asdf/issues/1640)) ([0c7c41a](https://github.com/asdf-vm/asdf/commit/0c7c41ab44d3a42a9e57e3d20a646569c2eacfdc)) * fix typo "node version" filename ([#1679](https://github.com/asdf-vm/asdf/issues/1679)) ([fad23bc](https://github.com/asdf-vm/asdf/commit/fad23bc9f4d38747f28d6708ab01689749030063)) * fix typo ([#1670](https://github.com/asdf-vm/asdf/issues/1670)) ([5737fa3](https://github.com/asdf-vm/asdf/commit/5737fa316eab01c4033565eacf678222cd861f8d)) * Improve `.asdfrc` plugin hook documentation ([#1661](https://github.com/asdf-vm/asdf/issues/1661)) ([8fbf9a3](https://github.com/asdf-vm/asdf/commit/8fbf9a396bd4a5b71ec7cf215d12040fb5365d6a)) ## [0.13.1](https://github.com/asdf-vm/asdf/compare/v0.13.0...v0.13.1) (2023-09-12) ### Patches * **fish:** use builtin realpath over system one ([#1637](https://github.com/asdf-vm/asdf/issues/1637)) ([5ac3032](https://github.com/asdf-vm/asdf/commit/5ac30328a7bbd1a8d974bb5fb1f14d8bd2d1e03f)) ## [0.13.0](https://github.com/asdf-vm/asdf/compare/v0.12.0...v0.13.0) (2023-09-11) ### ⚠ BREAKING CHANGES * `plugin list` exit code 0 when no plugins are installed ([#1597](https://github.com/asdf-vm/asdf/issues/1597)) * 0 exit code for success when adding an existing plugin ([#1598](https://github.com/asdf-vm/asdf/issues/1598)) * **fish:** don't resolve symlinks for ASDF_DIR ([#1583](https://github.com/asdf-vm/asdf/issues/1583)) ### Features * add plugin location when update the plugin ([#1602](https://github.com/asdf-vm/asdf/issues/1602)) ([36c7024](https://github.com/asdf-vm/asdf/commit/36c7024baa4b829b3629b4e0430157266d354158)) ### Patches * `plugin list` exit code 0 when no plugins are installed ([#1597](https://github.com/asdf-vm/asdf/issues/1597)) ([a029c00](https://github.com/asdf-vm/asdf/commit/a029c007503f2eec911a0c836e8622bb38c5e065)) * 0 exit code for success when adding an existing plugin ([#1598](https://github.com/asdf-vm/asdf/issues/1598)) ([4dd1904](https://github.com/asdf-vm/asdf/commit/4dd190466a9855dac300ce691e66a7629ef37b82)) * **fish:** don't resolve symlinks for ASDF_DIR ([#1583](https://github.com/asdf-vm/asdf/issues/1583)) ([d1a563d](https://github.com/asdf-vm/asdf/commit/d1a563dcc0107d5c631f73b114044898b5cadcf9)) * improve lint and test scripts ([#1607](https://github.com/asdf-vm/asdf/issues/1607)) ([b320803](https://github.com/asdf-vm/asdf/commit/b3208031204aabad6e85346155baacab16862da8)) * Make asdf.fish compatible with Fish 3.1.2 ([#1590](https://github.com/asdf-vm/asdf/issues/1590)) ([e83d71e](https://github.com/asdf-vm/asdf/commit/e83d71e43f525453994eb4cfda8ad66f8b914529)) * no longer write temporary files to home directory ([#1592](https://github.com/asdf-vm/asdf/issues/1592)) ([624604a](https://github.com/asdf-vm/asdf/commit/624604a8626dc6006d78121d4cf0f6c920449c56)) * nushell language syntax update ([#1624](https://github.com/asdf-vm/asdf/issues/1624)) ([0ddab5d](https://github.com/asdf-vm/asdf/commit/0ddab5dfaf28ad97c84a6aa56b08ccc212e07b4d)) * set default shell version values on POSIX entrypoint ([#1594](https://github.com/asdf-vm/asdf/issues/1594)) ([4d5f22d](https://github.com/asdf-vm/asdf/commit/4d5f22ddb89ce53e24b1ab1cbefce3be95238a19)) * warn when any ./lib/commands are marked as executable ([#1593](https://github.com/asdf-vm/asdf/issues/1593)) ([2043a09](https://github.com/asdf-vm/asdf/commit/2043a09574bdfdfcf2daf2fdb3bff2d9d2dad64e)) ### Documentation * `bin/latest-stable` empty query is set to default ([#1591](https://github.com/asdf-vm/asdf/issues/1591)) ([299dc97](https://github.com/asdf-vm/asdf/commit/299dc97a5b63d8afe1a0bba03e32dddfb7fb8e51)) * migrate to VitePress from VuePress ([#1578](https://github.com/asdf-vm/asdf/issues/1578)) ([5133819](https://github.com/asdf-vm/asdf/commit/5133819a77aaa393def347bfecb1c442ece4c7f8)) * upgrade deps & fix breaking changes ([446f8c5](https://github.com/asdf-vm/asdf/commit/446f8c5f947cc5f30f03403c2cfe4dec71b0a494)) ## [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) ### Bug Fixes * Prepend asdf directories to the PATH in Nushell ([#1496](https://github.com/asdf-vm/asdf/issues/1496)) ([745950c](https://github.com/asdf-vm/asdf/commit/745950c3589c4047a5b94b34bc9cf06dff5dc3c7)) ## [0.11.2](https://github.com/asdf-vm/asdf/compare/v0.11.1...v0.11.2) (2023-02-21) ### Bug Fixes * bash completion for latest command ([#1472](https://github.com/asdf-vm/asdf/issues/1472)) ([2606a87](https://github.com/asdf-vm/asdf/commit/2606a875eba8d74be56c78c97a76f3eb92c8253d)) * enforce & use consistent function definitions ([#1464](https://github.com/asdf-vm/asdf/issues/1464)) ([e0fd7a7](https://github.com/asdf-vm/asdf/commit/e0fd7a7be8bbbbf0f3cb6dc38cea3b62963eb0c9)) * nushell PATH conversion to list before filter ([#1471](https://github.com/asdf-vm/asdf/issues/1471)) ([cd0e12b](https://github.com/asdf-vm/asdf/commit/cd0e12b3ee4090242b884ac4aea0f65784e52946)) * Remove `==` inside `[` ([#1421](https://github.com/asdf-vm/asdf/issues/1421)) ([d81b81f](https://github.com/asdf-vm/asdf/commit/d81b81f9de2dc5961624464df04cef7cafae588c)) * support nushell v0.75.0 ([#1481](https://github.com/asdf-vm/asdf/issues/1481)) ([dd8d399](https://github.com/asdf-vm/asdf/commit/dd8d3999d41cfdd8518a9ea478929b5291b8838c)) ## [0.11.1](https://github.com/asdf-vm/asdf/compare/v0.11.0...v0.11.1) (2023-01-13) ### Bug Fixes * `reshim` did not rewrite executable path ([#1311](https://github.com/asdf-vm/asdf/issues/1311)) ([5af7625](https://github.com/asdf-vm/asdf/commit/5af76257693d1f560b9c27c9cdcc6f5a5a33c4d5)) * Add test for nushell integration and fix some bugs ([#1415](https://github.com/asdf-vm/asdf/issues/1415)) ([60d4494](https://github.com/asdf-vm/asdf/commit/60d4494d5d21f9d7bdd0778ca962ddb44280aff7)) * Allow `path:` versions to use `~` ([#1403](https://github.com/asdf-vm/asdf/issues/1403)) ([670c96d](https://github.com/asdf-vm/asdf/commit/670c96d1a6d6d2c19ff63ce2ed14f784c340e9b9)) * Ban use of 'test' ([#1383](https://github.com/asdf-vm/asdf/issues/1383)) ([ec972cb](https://github.com/asdf-vm/asdf/commit/ec972cbdf0acbecf70e3678c055e27866c49341d)) * correct order of checks in conditional for adding a missing newline ([#1418](https://github.com/asdf-vm/asdf/issues/1418)) ([4125d2b](https://github.com/asdf-vm/asdf/commit/4125d2b5560efc646e6048202ceb00fffd0b9eaf)), closes [#1417](https://github.com/asdf-vm/asdf/issues/1417) * Do not use `pwd` ([dd37b6f](https://github.com/asdf-vm/asdf/commit/dd37b6f0c0ed20d15e3d96b730db82f21c9e2e6f)) * Do not use type not exported on older Python versions ([#1409](https://github.com/asdf-vm/asdf/issues/1409)) ([7460809](https://github.com/asdf-vm/asdf/commit/74608098cdfc70c2d2e85d1f3861500ef668a041)) * force lwrcase plugin name fix capitalization mismatch errs ([#1400](https://github.com/asdf-vm/asdf/issues/1400)) ([196a05b](https://github.com/asdf-vm/asdf/commit/196a05b2dcef48f3a281350734c76ba7bc73fa81)) * lint errors from `scripts/checkstyle.py` ([#1385](https://github.com/asdf-vm/asdf/issues/1385)) ([3492043](https://github.com/asdf-vm/asdf/commit/3492043241e466337c5965a6fe2e089147bc4152)) * mv dev dep from repo root to subdir to avoid clash ([#1408](https://github.com/asdf-vm/asdf/issues/1408)) ([5df70da](https://github.com/asdf-vm/asdf/commit/5df70dadacd66b4150ed47e58c861418c0d1281f)) * Remove unnecessary backslashes ([#1384](https://github.com/asdf-vm/asdf/issues/1384)) ([15faf93](https://github.com/asdf-vm/asdf/commit/15faf93a0d3615834e550ea1562fb6b8cee5a205)) * Remove usage of `$(pwd)` in favor of `$PWD` ([f522ab9](https://github.com/asdf-vm/asdf/commit/f522ab98797345d767b239041246dfb4b740423e)) ## [0.11.0](https://github.com/asdf-vm/asdf/compare/v0.10.2...v0.11.0) (2022-12-12) ### Features * **completions:** bash improvements ([#1329](https://github.com/asdf-vm/asdf/issues/1329)) ([7c802c3](https://github.com/asdf-vm/asdf/commit/7c802c3fc9b5dc556993a98e5aaf96650cbb0d5b)) * Disable short-name repository with config value ([#1227](https://github.com/asdf-vm/asdf/issues/1227)) ([18caea3](https://github.com/asdf-vm/asdf/commit/18caea3eb7d989d195cf13b3c9ffc2058d906fc5)) * mark current resolved versions in `asdf list` output ([#762](https://github.com/asdf-vm/asdf/issues/762)) ([5ea6795](https://github.com/asdf-vm/asdf/commit/5ea67953be74cb5fde11240dc40a541c69afc65c)) * support nushell ([#1355](https://github.com/asdf-vm/asdf/issues/1355)) ([274a638](https://github.com/asdf-vm/asdf/commit/274a638e155c08cd0d6dbda1a0d4da02c3466c97)) ### Bug Fixes * add missing "does not add paths to PATH more than once" test for elvish ([#1275](https://github.com/asdf-vm/asdf/issues/1275)) ([3c55167](https://github.com/asdf-vm/asdf/commit/3c55167a6807b48cacaaed18df7bf0db2526ed59)) * append trailing newline to .tool-versions files when missing ([#1310](https://github.com/asdf-vm/asdf/issues/1310)) ([eb7dac3](https://github.com/asdf-vm/asdf/commit/eb7dac3a2b15ad458f55a897d49a377508ea92fe)), closes [#1299](https://github.com/asdf-vm/asdf/issues/1299) * excludes "milestone" releases in "latest" command ([#1307](https://github.com/asdf-vm/asdf/issues/1307)) ([5334d1d](https://github.com/asdf-vm/asdf/commit/5334d1db3d390c46ed49101528f74483eb6b2987)), closes [#1306](https://github.com/asdf-vm/asdf/issues/1306) * improve formatting of ballad ([#1367](https://github.com/asdf-vm/asdf/issues/1367)) ([e0c2c31](https://github.com/asdf-vm/asdf/commit/e0c2c31fc3274387efdddebe1450f0662f91a726)) * use ELVISH_VERSION to specify elvish test version ([#1276](https://github.com/asdf-vm/asdf/issues/1276)) ([72c3a23](https://github.com/asdf-vm/asdf/commit/72c3a2377a1afa3027c6f29cb9f3f1a7fbddaa8c)) ### [0.10.2](https://www.github.com/asdf-vm/asdf/compare/v0.10.1...v0.10.2) (2022-06-08) ### Bug Fixes * always use ASDF_DEFAULT_TOOL_VERSIONS_FILENAME for filename when present ([#1238](https://www.github.com/asdf-vm/asdf/issues/1238)) ([711ad99](https://www.github.com/asdf-vm/asdf/commit/711ad991043a1980fa264098f29e78f2ecafd610)), closes [#1082](https://www.github.com/asdf-vm/asdf/issues/1082) * get invalid ASDF_DATA_DIR when exec asdf shims by non-shell ([#1154](https://www.github.com/asdf-vm/asdf/issues/1154)) ([b9962f7](https://www.github.com/asdf-vm/asdf/commit/b9962f71564ce77cf97772cc100b80f9d77019b1)) * update event trigger for doc-version workflow ([#1232](https://www.github.com/asdf-vm/asdf/issues/1232)) ([0bc8c3a](https://www.github.com/asdf-vm/asdf/commit/0bc8c3ab6895b88c96bff86f5f79575ee80cc718)) * update plugin-add regex to support other languages ([#1241](https://www.github.com/asdf-vm/asdf/issues/1241)) ([92d005d](https://www.github.com/asdf-vm/asdf/commit/92d005dacd2ec434a9d912ab9938b59ab1b7c51f)), closes [#1237](https://www.github.com/asdf-vm/asdf/issues/1237) * updating references to legacy github.io site ([#1240](https://www.github.com/asdf-vm/asdf/issues/1240)) ([738306b](https://www.github.com/asdf-vm/asdf/commit/738306bc5d1c53a22c06e4d6d3ddb6d511dc5d50)) ### [0.10.1](https://www.github.com/asdf-vm/asdf/compare/v0.10.0...v0.10.1) (2022-05-17) ### Bug Fixes * add asdf to list of banned commands ([#1224](https://www.github.com/asdf-vm/asdf/issues/1224)) ([39909e0](https://www.github.com/asdf-vm/asdf/commit/39909e01af2bbf23fc821de5cec6c5c9661c59bb)) * don't invoke asdf inside asdf commands ([#1208](https://www.github.com/asdf-vm/asdf/issues/1208)) ([27f7ef7](https://www.github.com/asdf-vm/asdf/commit/27f7ef78529649534b8383daa58e4b370b3cbd7f)) * fixing bats ([#1215](https://www.github.com/asdf-vm/asdf/issues/1215)) ([a9caa5b](https://www.github.com/asdf-vm/asdf/commit/a9caa5bdffca5401798fb37e6f34af933b6ce0d2)) * instead /tmp, use TMPDIR if defined ([9113623](https://www.github.com/asdf-vm/asdf/commit/91136234e90b5fe8718338f513fa770c99151d3e)) * make fish shell setup match other shells ([#1209](https://www.github.com/asdf-vm/asdf/issues/1209)) ([6fc4bb8](https://www.github.com/asdf-vm/asdf/commit/6fc4bb8fc650e73152ce326267f89df6865cdd24)) * only iterate over directories in the plugins/ directory ([#1228](https://www.github.com/asdf-vm/asdf/issues/1228)) ([788ccab](https://www.github.com/asdf-vm/asdf/commit/788ccab5971cb828cf25364b0df5ed6f5e9e713d)) * update elvish to 0.18.0 ([5a89563](https://www.github.com/asdf-vm/asdf/commit/5a89563c0a37f244fa3daa46c5100b7711edde1d)) ## [0.10.0](https://www.github.com/asdf-vm/asdf/compare/v0.9.0...v0.10.0) (2022-04-14) ### Features * case-insensitive filtering of unstable versions in `latest` ([#1139](https://www.github.com/asdf-vm/asdf/issues/1139)) ([e61e3d9](https://www.github.com/asdf-vm/asdf/commit/e61e3d9ade0d7bdfb4413184284038c50ba1e09c)) * **latest:** adds the flag --all to the latest command ([#1096](https://www.github.com/asdf-vm/asdf/issues/1096)) ([f85fef5](https://www.github.com/asdf-vm/asdf/commit/f85fef533f249df5a9f58307d288f2f069351e88)) * upgrade elvish to 0.17.0 ([#1159](https://www.github.com/asdf-vm/asdf/issues/1159)) ([824550e](https://www.github.com/asdf-vm/asdf/commit/824550ed2009c7e8c4c84afd7a01197d451c47bf)) ### Bug Fixes * Ban `ls` command ([#1141](https://www.github.com/asdf-vm/asdf/issues/1141)) ([87137e4](https://www.github.com/asdf-vm/asdf/commit/87137e41031f17b30acf12ee35925e689c84e2d8)) * ban grep long flags ([#1117](https://www.github.com/asdf-vm/asdf/issues/1117)) ([6e4c39c](https://www.github.com/asdf-vm/asdf/commit/6e4c39c244a289a54f235cf15a29874fb8885927)) * do not print `find` errors ([#1102](https://www.github.com/asdf-vm/asdf/issues/1102)) ([5992abb](https://www.github.com/asdf-vm/asdf/commit/5992abb09e6f5e0af690bf0e99619386187949db)) * don't generate on error if backup file doesn't exists ([#1057](https://www.github.com/asdf-vm/asdf/issues/1057)) ([288468f](https://www.github.com/asdf-vm/asdf/commit/288468f93f6c5cb4e7cca1173d4ad73154d0d564)) * **elvish:** prepend asdf paths to `$PATH` ([#1174](https://www.github.com/asdf-vm/asdf/issues/1174)) ([682b7a1](https://www.github.com/asdf-vm/asdf/commit/682b7a1d6dc1a35f7f8b0ddbab977e0a3dae2c9c)) * latest --all correctly report plugins as missing ([#1118](https://www.github.com/asdf-vm/asdf/issues/1118)) ([aafe1e5](https://www.github.com/asdf-vm/asdf/commit/aafe1e5304c2d2a026831976c18faa6fb48d25bc)) * local plugin in then clause too ([#1203](https://www.github.com/asdf-vm/asdf/issues/1203)) ([448f750](https://www.github.com/asdf-vm/asdf/commit/448f750891a4366f45d905b112ad20d4be66c496)) * newline after error msg for ASDF_DIR ([#1113](https://www.github.com/asdf-vm/asdf/issues/1113)) ([ac2791e](https://www.github.com/asdf-vm/asdf/commit/ac2791e49f7fcdbeb420415d8ddcb5f17bcf296e)) * Prevent unbound variable error with nounset in asdf.sh ([#1158](https://www.github.com/asdf-vm/asdf/issues/1158)) ([b7dd291](https://www.github.com/asdf-vm/asdf/commit/b7dd291c983af321af20550fa89bf1cfbc888aec)) * remove comments from whole file instead of line by line for performance ([#1198](https://www.github.com/asdf-vm/asdf/issues/1198)) ([de6e22f](https://www.github.com/asdf-vm/asdf/commit/de6e22f909946f7d17047f4aeab41e581546b674)) * shorthand grep options for alpine support ([#1106](https://www.github.com/asdf-vm/asdf/issues/1106)) ([234778a](https://www.github.com/asdf-vm/asdf/commit/234778a397f19c398d2f76a0321fef3273c9a086)) ## [0.9.0](https://www.github.com/asdf-vm/asdf/compare/v0.8.1...v0.9.0) (2021-11-18) ### Features * add post update plugin support ([#1049](https://www.github.com/asdf-vm/asdf/issues/1049)) ([304f72d](https://www.github.com/asdf-vm/asdf/commit/304f72dbb207606fd82c04ee2c73cf11e9e6e0cc)) * asdf latest defer to plugin to determine the latest version ([#938](https://www.github.com/asdf-vm/asdf/issues/938)) ([664d82e](https://www.github.com/asdf-vm/asdf/commit/664d82ed8a734eb30988840829a972f8ddd8e523)) * configurable plugin repo last check time ([#957](https://www.github.com/asdf-vm/asdf/issues/957)) ([1716afa](https://www.github.com/asdf-vm/asdf/commit/1716afa02125aa322d8a688ff4b3e95f2e08b33c)) * display plugin repo refs alongside urls in info cmd ([#1014](https://www.github.com/asdf-vm/asdf/issues/1014)) ([cd0a6a7](https://www.github.com/asdf-vm/asdf/commit/cd0a6a779eb18236fe7bf1f84403e33e636ef1f3)) * Displays a warning when a plugin from the tools-version list does not exist ([#1033](https://www.github.com/asdf-vm/asdf/issues/1033)) ([9430a39](https://www.github.com/asdf-vm/asdf/commit/9430a39aef1dbf806a8954d71711747be1001076)) * Elvish Shell support ([#1066](https://www.github.com/asdf-vm/asdf/issues/1066)) ([cc7778a](https://www.github.com/asdf-vm/asdf/commit/cc7778a040751f6801524135f5f5ece3a748fa8c)) * toggle off repo sync completely ([#1011](https://www.github.com/asdf-vm/asdf/issues/1011)) ([a3ba5a7](https://www.github.com/asdf-vm/asdf/commit/a3ba5a794c07efb4aa9cce9c15d41b4b61d5df01)) ### Bug Fixes * Adds "grep -P" to the list of banned commands ([#1064](https://www.github.com/asdf-vm/asdf/issues/1064)) ([8a515f4](https://www.github.com/asdf-vm/asdf/commit/8a515f49d7443ee318badbd4d8f092ad0d8f04ca)) * allow plugin callbacks to be in any language ([#995](https://www.github.com/asdf-vm/asdf/issues/995)) ([2ad0f5e](https://www.github.com/asdf-vm/asdf/commit/2ad0f5ea452bd8f843951c4a9cc56a020e172b07)) * clarify the wording when no version is set ([#1088](https://www.github.com/asdf-vm/asdf/issues/1088)) ([4116284](https://www.github.com/asdf-vm/asdf/commit/41162849cf5c966c749ec435ebe32bd649a86ee8)) * completions for asdf plugin list ([#1061](https://www.github.com/asdf-vm/asdf/issues/1061)) ([43412aa](https://www.github.com/asdf-vm/asdf/commit/43412aad5f668686daa058505a61c070561b46fc)) * Correct typo on getting started page ([#1086](https://www.github.com/asdf-vm/asdf/issues/1086)) ([4321980](https://www.github.com/asdf-vm/asdf/commit/4321980c3385ac1bafd77503c8ec77b26042d05b)) * don't override existing ASDF_DIR ([#1008](https://www.github.com/asdf-vm/asdf/issues/1008)) ([73efc9f](https://www.github.com/asdf-vm/asdf/commit/73efc9fa97744c49c5004ee8bb9b6064b6ce22f2)) * ensure shims get created when data dir has spaces ([#996](https://www.github.com/asdf-vm/asdf/issues/996)) ([39c9999](https://www.github.com/asdf-vm/asdf/commit/39c9999519a1d3c51ffb3b8dddd141dbc29b3bd1)) * Fix plugin-test arg parsing ([#1084](https://www.github.com/asdf-vm/asdf/issues/1084)) ([c911f2d](https://www.github.com/asdf-vm/asdf/commit/c911f2d43198945f21bb25100c9dab5a375c780b)) * full_version_name is not available here ([#1031](https://www.github.com/asdf-vm/asdf/issues/1031)) ([8490526](https://www.github.com/asdf-vm/asdf/commit/84905265467c9fdf618c11f69a5ae71408e18bea)) * help for extension commands for plugins with hyphens in the name. ([#1048](https://www.github.com/asdf-vm/asdf/issues/1048)) ([3e0cb9a](https://www.github.com/asdf-vm/asdf/commit/3e0cb9aaea7f2bf282a18c4912454737fef0741b)) * help text as per new feats in [#633](https://www.github.com/asdf-vm/asdf/issues/633) ([#991](https://www.github.com/asdf-vm/asdf/issues/991)) ([0d95663](https://www.github.com/asdf-vm/asdf/commit/0d956635b5cabe35f0895121929e8e668a3ee03d)) * incorrect usage of grep ([#1035](https://www.github.com/asdf-vm/asdf/issues/1035)) ([30d27cb](https://www.github.com/asdf-vm/asdf/commit/30d27cbe6b358cd790fb66dbc8a14806eca23f05)) * insert error handling in list-all & download plugin scripts ([#881](https://www.github.com/asdf-vm/asdf/issues/881)) ([a7d3661](https://www.github.com/asdf-vm/asdf/commit/a7d3661f6c53b24ae1c21e93f94209f3af243349)) * lint scripts for local and CI ([#961](https://www.github.com/asdf-vm/asdf/issues/961)) ([5dafbc8](https://www.github.com/asdf-vm/asdf/commit/5dafbc8e390eacbcfcf97d6d2890e0aa6ef9cd60)) * pipe find into while ([26d2c64](https://www.github.com/asdf-vm/asdf/commit/26d2c64477a1faabedd9a5f97aa7da706988cd72)) * Quote commands correctly in plugin-test ([#1078](https://www.github.com/asdf-vm/asdf/issues/1078)) ([69ff2d0](https://www.github.com/asdf-vm/asdf/commit/69ff2d0c9a4fd273c9dac151345f60f7b146e569)) * regex validate plugin names on plugin add cmd ([#1010](https://www.github.com/asdf-vm/asdf/issues/1010)) ([7697e6e](https://www.github.com/asdf-vm/asdf/commit/7697e6e344809ab4603d0764fb8a969c2bbaf3b6)) * remove find -print0 ([b9228a2](https://www.github.com/asdf-vm/asdf/commit/b9228a26de6a0337a7b59fb5252323d368a72a92)) * Sed improvements ([#1087](https://www.github.com/asdf-vm/asdf/issues/1087)) ([4b93bc8](https://www.github.com/asdf-vm/asdf/commit/4b93bc81aa982b72621cd09e71eeea71ee009185)) * sed re error trailing backslash on FreeBSD ([#1046](https://www.github.com/asdf-vm/asdf/issues/1046)). ([#1047](https://www.github.com/asdf-vm/asdf/issues/1047)) ([47e8fb0](https://www.github.com/asdf-vm/asdf/commit/47e8fb051b3577d251376976d5767c520f3524fc)) * support latest with filter on local and global ([#633](https://www.github.com/asdf-vm/asdf/issues/633)) ([5cf8f89](https://www.github.com/asdf-vm/asdf/commit/5cf8f8962fbd5fe2bc86856bc4676f88e1aa8885)) * Use more idiomatic fish ([#1042](https://www.github.com/asdf-vm/asdf/issues/1042)) ([847ec73](https://www.github.com/asdf-vm/asdf/commit/847ec73751ced9d149ce0826309fee0f894ca664)) * wait until the plugin update are finished ([#1037](https://www.github.com/asdf-vm/asdf/issues/1037)) ([7e1f2a0](https://www.github.com/asdf-vm/asdf/commit/7e1f2a0d938052d4fa5ce6546f07b3decbd740cf)) ## 0.8.1 Features * Support for latest version in shell, local, and global commands (#802, #801) * Parallel updating of all plugins (#626, #530) * Print documentation website and GitHub URLs in help command (#820) Fixed Bugs * Fix plugin-update --all when there are no plugins (#805, #803) * Ban `echo` command from asdf codebase (#806, #781) * Add basic tests for plugin-update command (#807) * Cleanup unused code in plugin update tests (#810) * Fix resolution of relative symlinks (#815, #625) * Fixes to GitHub workflow (#833) * Update no plugin installed error message (#818) * Remove process substitution that was problematic when POSIXLY_CORRECT is set (#851, #581) * Fix warnings from find command (#853) * Ban the `sort -V` command from the asdf codebase (#755, #867) * Fix `plugin update --all` so that the default branch is used for each plugin (#800) * Fix issues with awk command on some platforms used by plugin update command (#924, #899, #919) * Add completion for the `system` version (#911) Documentation * Link to Homebrew common issues from documentation site (#795) * Remove -vm suffix name in documentation (#798, #796) * Fix file renames in release script (#809) * Update supported versions in documentation (#825) * Fix references to icongram files (#827) * Fix broken links in CONTRIBUTING.md (#832, #852) * Fix broken link in README.md (#835) * Improve zsh completion directions for macOS,ZSH,Homebrew (#843) * Add GitHub discussions link (#839) * Add note about unsolicited formatting pull requests (#848) * Fix formatting of GitHub name (#847) * Explain the difference between ASDF_DIR and ASDF_DATA_DIR (#855) * Update BATS link to bats-core GitHub repo (#858) * Instruct users to symlink completions for Fish shell (#860) * Support alternate locations for `.zshrc` (#871) * Add "Add translation" link to navbar (#876) * Clarify usage of the ASDF_DEFAULT_TOOL_VERSIONS_FILENAME variable (#912, #900) * Show how to use the `system` version (#925, #868) * Remove instructions for installing dependencies for Homebrew installs (#937, #936) ## 0.8.0 Features * Add support for plugin documentation callback scripts (#512, #757) * Add support for installing one tool specified in `.tool-versions` (#759, #760) * Improve introduction and install sections of documentation (#699, #740) * Add dependencies for openSUSE and ArchLinux to documentation (#714) * Add support for keeping downloaded tool source code (#74, #669) * Add `asdf info` command to print debug information (#786, #787) Fixed Bugs * Fix typo that caused plugin-test to erroneously fail (#780) * Make sure shims are only appended to `PATH` once in Fish shell (#767, #777, #778) * Print `.tool-versions` file path on shim error (#749, #750) * Add `column` and `sort -V` to list of banned commands for the asdf codebase (#661, #754) * Use editorconfig for shell formatting (#751) * Remove use of `column` command in favor of awk (#721) * Add `asdf shell` command to help output (#715, #737) * Ensure consistency in indentation for message shown when no versions installed (#728) * Fix dead link in documentation (#733) * Fix typo in docs/core-manage-versions.md (#722) * Fix a typo in the `asdf env` command documentation (#717) * Fix Fish shell documentation (#709) * Only list asdf dependencies on asdf website (#511, #710) * Add CODEOWNERS file for GitHub reviews (#705) * Add unit test for `asdf plugin-add` exit code (#689) ## 0.7.8 Features * Add support for `post-plugin-add` and `pre-plugin-remove` in plugins. Add configurable command hooks for plugin installation and removal (#670, #683) ```shell pre_asdf_plugin_remove = echo will remove plugin ${1} pre_asdf_plugin_remove_foo = echo will remove plugin foo post_asdf_plugin_remove = echo removed plugin ${1} post_asdf_plugin_remove_foo = echo removed plugin foo ``` * Use different exit code if updates are disabled (#676) Fixed Bugs * Make sure extension commands are properly displayed by `asdf help` Extension commands are now expected to be inside plugins's `lib/commands/command-*.bash` instead of `bin/command*`. This change was made for two reasons: Keep the convention that all files to be sourced by bash should end with the `.bash` extension. And the `lib/commands/` directory mirrors the location of asdf own core commands. Added tests to make sure `asdf help` properly displays available extension commands. * Remove automatic `compinit` from asdf.sh (#674, #678) ## 0.7.7 Features * Add .bash file extension to files executed by Bash (#664) * Add security policy (#660) Fixed Bugs * consistent use of plugin_name (#657) * Default ZSH_VERSION to empty string (#656) * Fix support for path version (#654) * Fix hanging 'asdf update is a noop for non-git repos' test (#644) * Fix Bash completions for `plugin-add` (#643) * Fix `--unset` for Fish shell (#640) * Misc. documentation fixes (#631, #652) * Defaults to empty ASDF_DATA_DIR (#630) * Remove shebang lines of sourced scripts (#629) * Ignore shim directory for executable lookups (#623) * Fix issue with preset version warning assuming that the shim name and plugin name are the same (#622) ## 0.7.6 Features * Improve output format of `asdf plugin list all` Long plugin names were causing problems with how we used printf. Now we use the `column` command to properly render output. * Now `asdf plugin list` can take both `--urls` and `--refs` options. When `--url` is used, we print the plugin's remote origin URL. While `--refs` prints the git branch/commit the plugin is at. * It's now possible to update a plugin to an specific branch/commit. `asdf plugin update [git-ref]` Checkouts a plugin to the specified `git-ref`. Defaults to `master` * Now the `asdf plugin test` command can be specified with a plugin commit/branch to test. This will help CI checks to actually test the commit they are running for. Previously we always used the plugin's `master` branch. * Subcommand CLI support. Users familiar with sub-command aware tools like `git` can now use `asdf` commands in the same way. For example: `asdf plugin list all` is equivalent to `asdf plugin-list-all` This is also the case for plugin extension commands, where the plugin name is an asdf main subcommand. ie. Having a `foo` plugin you can invoke: `asdf foo bar` * Make `asdf plugin test` use the new `asdf latest` command. (#541) If a plugin version is not given explicitly, we use `asdf latest` to obtain the version of plugin to install for testing. * `asdf --version` displays git revision when asdf_dir is a git clone. This will allow better bug reports since people can now include the git commit they are using. * Add support for asdf extension commands. Plugins can provide `bin/command*` scripts or executables that will be callable using the asdf command line interface. See `docs/plugins-create.md` for more info. * Add support for installing the latest stable version of a tool (#216) ```shell asdf install python latest asdf install python latest:3.7 # installs latest Python 3.7 version ``` * Add `asdf latest` command to display the latest stable version of a tool (#575) ```shell asdf latest python asdf latest python 3.7 # displays latest Python 3.7 version ``` * Add support for filtering versions returned by `asdf list-all` ```shell asdf list-all python 3.7 # lists available Python 3.7 versions ```` ## 0.7.5 Features * Add AppVeyor config for builds on Windows, for eventual Windows support (#450, #451) * Add `--unset` flag to shell command (#563) Fixed Bugs * Fix multiple version install (#540, #585) * Handle dashes in executable/shim names properly (#565, #589) * Fix bug in sed command so `path:...` versions are handled correctly (#559, #591) ## 0.7.4 Features * Add quite flag to git clone (#546) * Improve docs for Homebrew (#553, #554) Fixed Bugs * Don't include the current directory in `PATH` variable in `asdf env` environment (#543, #560) * Fix `asdf plugin-test` dependency on Git when installed via Homebrew (#509, #556) ## 0.7.3 Features * Make `asdf install` check for versions in legacy files (#533, #539) Fixed Bugs * Address shellcheck warning and use shell globbing instead of `ls` (#525) ## 0.7.2 Features * Add unit tests for untested code in asdf.sh and asdf.fish (#286, #507, #508) * Switched to a maintained version of BATS (#521) Fixed Bugs * Don't iterate on output of `ls` (#513) * Check shims for full tool version so adding new versions to a shim works properly (#517, #524) ## 0.7.1 Features * Add mksh support * Add documentation about using multiple versions of the same plugin * Remove post_COMMAND hooks * Add `asdf shell` command to set a version for the current shell (#480) * Ignore comments in .tool-versions (#498, #504) Fixed Bugs * Avoid modifying `fish_user_paths` * Restore support for legacy file version (#484) * Restore support for multiple versions * Fix bug when trying to locate shim (#488) * Run executable using `exec` (#502) ## 0.7.0 Features * Shims can be invoked directly via `asdf exec [args...]` without requiring to have all shims on path (#374). * New `asdf env ` can be used to print or execute with the env that would be used to execute a shim. (#435) * Configurable command hooks from `.asdfrc` (#432, #434) Suppose a `foo` plugin is installed and provides a `bar` executable, The following hooks will be executed when set: ```shell pre_asdf_install_foo = echo will install foo version ${1} post_asdf_install_foo = echo installed foo version ${1} pre_asdf_reshim_foo = echo will reshim foo version ${1} post_asdf_reshim_foo = echo reshimmed foo version ${1} pre_foo_bar = echo about to execute command bar from foo with args: ${@} post_foo_bar = echo just executed command bar from foo with args: ${@} pre_asdf_uninstall_foo = echo will remove foo version ${1} post_asdf_uninstall_foo = echo removed foo version ${1} ``` * New shim version meta-data allows shims to not depend on a particular plugin nor on its relative executable path (#431) Upgrading requires shim re-generation and should happen automatically by `asdf-exec`: `rm -rf ~/.asdf/shims/` followed by `asdf reshim` * Added lots of tests for shim execution. We now make sure that shim execution obeys plugins hooks like `list-bin-paths` and `exec-path`. * Shims now are thin wrappers around `asdf exec` that might be faster for most common use case: (versions on local .tool-versions file) but fallbacks to slower `get_preset_version_for` which takes legacy formats into account. * Shim exec recommends which plugins or versions to set when command is not found. * `asdf reshim` without arguments now reshims all installed plugins (#407) * Add `asdf shim-versions ` to list on which plugins and versions is a command available. (#380, #433) * Add documentation on installing dependencies via Spack (#471) Fixed Bugs * Fix `update` command so it doesn't crash when used on Brew installations (#429, #474, #439, #436) ## 0.6.3 Features * Make `which` command work with any binary included in a plugin installation (#205, #382) * Add documentation for documentation website (#274, #396, #422, #423, #427, #430) Fixed Bugs * Silence errors during tab completion (#404) * Remove unused asdf shims directory from `PATH` (#408) * Fix issues with update command that prevented updates for installations in custom locations (#411) * Fix shellcheck warnings on OSX (#416) * Add tests for versions set by environment variables (#417, #327) * Continue `list` output even when version is not found (#419) * Fixed user paths for fish (#420, #421) * Custom exec path tests (#324, #424) ## 0.6.2 Fixed Bugs * Fix `system` logic so shims directory is removed from `PATH` properly (#402, #406) * Support `.tool-versions` files that don't end in a newline (#403) ## 0.6.1 Features * Make `where` command default to current version (#389) * Optimize code for listing all plugins (#388) * Document `$TRAVIS_BUILD_DIR` in the plugin guide (#386) * Add `--asdf-tool-version` flag to plugin-test command (#381) * Add `-p` flag to `local` command (#377) Fixed Bugs * Fix behavior of `current` command when multiple versions are set (#401) * Fix fish shell init code (#392) * Fix `plugin-test` command (#379) * Add space before parenthesis in `current` command output (#371) ## 0.6.0 Features * Add support for `ASDF_DATA_DIR` environment variable (#275, #335, #361, #364, #365) Fixed Bugs * Fix `asdf current` so it works when no versions are installed (#368, #353) * Don't try to install system version (#369, #351) * Make `resolve_symlink` function work with relative symlinks (#370, #366) * Fix version changing code so it preserves symlinks (#329, #337) * Fix ShellCheck warnings (#336) ## 0.5.1 Features * Better formatting for `asdf list` output (#330, #331) Fixed Bugs * Correct environment variable name used for version lookup (#319, #326 #328) * Remove unnecessary `cd` in `asdf.sh` (#333, #334) * Correct Fish shell load script (#340) ## 0.5.0 Features * Changed exit codes for shims so we use codes with special meanings when possible (#305, #310) * Include plugin name in error message if plugin doesn't exist (#315) * Add support for custom executable paths (#314) * `asdf list` with no arguments should list all installed versions of all plugins (#311) Fixed Bugs * Print "No version set" message to stderr (#309) * Fix check for asdf directories in path for Fish shell (#306) ## 0.4.3 Features * Suggest action when no version is set (#291, #293) Fixed Bugs * Fix issue with asdf not always being added to beginning of `$PATH` (#288, #303, #304) * Fix incorrect `ASDF_CONFIG_FILE` environment variable name (#300) * Fix `asdf current` so it shows environment variables that are setting versions (#292, 294) ## 0.4.2 Features * Add support for `ASDF_DEFAULT_TOOL_VERSIONS_FILENAME` environment variable (#201, #228) * Only add asdf to `PATH` once (#261, #271) * Add `--urls` flag to `plugin-list` commands (#273) Fixed Bugs * Incorrect `grep` command caused version command to look at the wrong tool when reporting the version (#262) ## 0.4.1 Features * `asdf install` will also search for `.tool-versions` in parent directories (#237) Fixed Bugs * bad use of `sed` caused shims and `.tool-versions` to be duplicated with `-e` (#242, #250) * `asdf list` now outputs ref-versions as used on `.tool-versions` file (#243) * `asdf update` will explicitly use the `origin` remote when updating tags (#231) * All code is now linted by shellcheck (#223) * Add test to fail builds if banned commands are found (#251) ## 0.4.0 Features * Add CONTRIBUTING guidelines and GitHub issue and pull request templates (#217) * Add `plugin-list-all` command to list plugins from asdf-plugins repo. (#221) * `asdf current` shows all current tool versions when given no args (#219) * Add asdf-plugin-version metadata to shims (#212) * Add release.sh script to automate release of new versions (#220) Fixed Bugs * Allow spaces on path containing the `.tool-versions` file (#224) * Fixed bug in `--version` functionality so it works regardless of how asdf was installed (#198) ## 0.3.0 Features * Add `update` command to make it easier to update asdf to the latest release (#172, #180) * Add support for `system` version to allow passthrough to system installed tools (#55, #182) Fixed Bugs * Set `GREP_OPTIONS` and `GREP_COLORS` variables in util.sh so grep is always invoked with the correct settings (#170) * Export `ASDF_DIR` variable so the Zsh plugin can locate asdf if it's in a custom location (#156) * Don't add execute permission to files in a plugin's bin directory when adding the plugin (#124, #138, #154) ## 0.2.1 Features * Determine global tool version even when used outside of home directory (#106) Fixed Bugs * Correct reading of `ref:` and `path:` versions (#112) * Remove shims when uninstalling a version or removing a plugin (#122, #123, #125, #128, #131) * Add a helpful error message to the install command (#135) ## 0.2.0 Features * Improve plugin API for legacy file support (#87) * Unify `asdf local` and `asdf global` version getters as `asdf current` (#83) * Rename `asdf which` to `asdf current` (#78) Fixed Bugs * Fix bug that caused the `local` command to crash when the directory contains whitespace (#90) * Misc typo corrections (#93, #99) ## 0.1.0 * First tagged release ================================================ FILE: CONTRIBUTING.md ================================================ # How to Contribute to asdf There are many ways to contribute to `asdf`, thanks for taking the time to read and help. ## `asdf` Core ### Did you find a bug? Ensure the bug is actually an issue with asdf and not a plugin. If the bug only occurs when using one tool installed by `asdf` and not others it is likely an issue with the **plugin**. Find the plugin repo URL with `asdf plugin list --urls`, browse their repo Issues and if no solution is found open a new Issue there. Ensure the bug was not already reported in existing [Issues](https://github.com/asdf-vm/asdf/issues). If not, then please [open a new Issue](https://github.com/asdf-vm/asdf/issues/new/choose). Please be as specific as possible when reporting the issue. ### New Features/Proposals Please [open a new Feature Request](https://github.com/asdf-vm/asdf/issues/new/choose) to discuss the feature before implementing a solution. ### Developing with the Core See [docs/contribute/core.md](docs/contribute/core.md) or on our [Docs Site](https://asdf-vm.com/contribute/core.html). ## Documentation Documentation can always be improved! See [docs/contribute/documentation.md](docs/contribute/documentation.md) or on our [Docs Site](https://asdf-vm.com/contribute/documentation.html). ## First-Party Plugins We always need help to maintain our plugins. See [docs/contribute/first-party-plugins.md](docs/contribute/first-party-plugins.md) or on our [Docs Site](https://asdf-vm.com/contribute/first-party-plugins.html). ## Create a Plugin? Please read the [docs/plugins/create.md](docs/plugins/create.md) or on our [Docs Site](https://asdf-vm.com/plugins/create.html). ## GitHub Action See the [asdf actions repo](https://github.com/asdf-vm/actions) for existing Issues, conversations and Contributing Guidelines. --- Thanks for contributing! ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2014 Akash Manohar J Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ MAIN_PACKAGE_PATH := ./cmd/asdf TARGET_DIR := . TARGET := asdf LINKER_FLAGS = '-s' # Not sure what the default location should be for builds build: # test lint go build -ldflags=${LINKER_FLAGS} -o=${TARGET_DIR}/${TARGET} ${MAIN_PACKAGE_PATH} fmt: go fmt ./... go run mvdan.cc/gofumpt -l -w . verify: go mod verify tidy: go mod tidy -v audit: verify vet test test: go test -coverprofile=/tmp/coverage.out -bench= -race ./... cover: test go tool cover -html=/tmp/coverage.out lint: fmt go run honnef.co/go/tools/cmd/staticcheck -tests -show-ignored ./... go run github.com/mgechev/revive -set_exit_status ./... vet: fmt go vet ./... run: build ${TARGET_DIR}/${TARGET} .PHONY: fmt lint vet build test run ================================================ FILE: README.md ================================================ # asdf [![Lint](https://github.com/asdf-vm/asdf/actions/workflows/lint.yml/badge.svg)](https://github.com/asdf-vm/asdf/actions/workflows/lint.yml) [![Tests](https://github.com/asdf-vm/asdf/actions/workflows/tests.yml/badge.svg)](https://github.com/asdf-vm/asdf/actions/workflows/tests.yml) **Manage multiple runtime versions with a single CLI tool, extendable via plugins** - [docs at asdf-vm.com](https://asdf-vm.com/) asdf is a CLI tool that can manage multiple language runtime versions on a per-project basis. It is like `gvm`, `nvm`, `rbenv` & `pyenv` (and more) all in one! Simply install your language's plugin! ## Why use asdf? - single CLI for multiple languages - consistent commands to manage all your languages - single global config keeping defaults in one place - single `.tool-versions` config file per project - support for existing config files `.node-version`, `.nvmrc`, `.ruby-version` for easy migration - automatically switches runtime versions as you traverse your directories - simple plugin system to add support for your language of choice - shell completion available for common shells (Bash, Zsh, Fish, Elvish) ## Documentation [Please head over to the documentation site for more information](https://asdf-vm.com/)! - [Getting Started](https://asdf-vm.com/guide/getting-started.html) - [All Commands](https://asdf-vm.com/manage/commands.html) - [All Plugins](https://github.com/asdf-vm/asdf-plugins) - [Create a Plugin](https://asdf-vm.com/plugins/create.html) with our [asdf-plugin-template](https://github.com/asdf-vm/asdf-plugin-template) - [asdf GitHub Actions](https://github.com/asdf-vm/actions) ## Contributing See [CONTRIBUTING.md in the repo](https://github.com/asdf-vm/asdf/blob/master/CONTRIBUTING.md) or the [Contributing section on the docs site](http://asdf-vm.com/contribute/core.html#initial-setup). ## Community & Questions - [FAQ](https://asdf-vm.com/more/faq.html) - [![GitHub Issues](https://icongr.am/simple/github.svg?color=808080&size=16) GitHub Issues](https://github.com/asdf-vm/asdf/issues): report a bug or raise a feature request to the `asdf` core team - [![StackOverflow Tag](https://icongr.am/fontawesome/stack-overflow.svg?size=16&color=808080) StackOverflow Tag](https://stackoverflow.com/questions/tagged/asdf-vm): see existing Q&A for `asdf`. Some of the core team watch this tag in addition to our helpful community ## Ballad of asdf > Once upon a time there was a programming language
> There were many versions of it
> So people wrote a version manager for it
> To switch between versions for projects
> Different, old, new. > > Then there came more programming languages
> So there came more version managers
> And many commands for them > > I installed a lot of them
> I learnt a lot of commands > > Then I said, just one more version manager
> Which I will write instead > > So, there came another version manager
> **asdf version manager** - > > A version manager so extendable
> for which anyone can create a plugin
> To support their favourite language
> No more installing more version managers
> Or learning more commands ---
This was the mail I wrote to a few friends to tell them about the project. Thanks to @roshanvid for suggesting that this go into the readme
@HashNuke
================================================ FILE: SECURITY.md ================================================ # Security Policy **This security policy only applies to asdf core, which is the code contained in this repository. All plugins are the responsibility of their creators and are not covered under this security policy.** ## Supported Versions ``` 0.18.1 ``` Typically only the latest version is maintained. Versions are released sequentially, with major, minor, and patch versions incremented as necessary. If a vulnerability is found in an old version of asdf, updating to the latest version of asdf will be required. ## Reporting a Vulnerability To report a vulnerability please contact one of: - @Stratus3D via email: see https://stratus3d.com/contact/ for email. PGP is not setup for this email address. - @danhper via email: see https://daniel.perez.sh/ for email and PGP key - @jthegedus via email: see https://github.com/jthegedus for email. PGP is not setup for this email address. ================================================ FILE: asdf.elv ================================================ use path use re use str var asdf_dir = ~'/.asdf' if (and (has-env ASDF_DIR) (!=s $E:ASDF_DIR '')) { set asdf_dir = $E:ASDF_DIR } else { set-env ASDF_DIR $asdf_dir } var asdf_data_dir = $asdf_dir if (and (has-env ASDF_DATA_DIR) (!=s $E:ASDF_DATA_DIR '')) { set asdf_data_dir = $E:ASDF_DATA_DIR } # Add function wrapper so we can export variables fn asdf {|command @args| if (==s $command 'shell') { # set environment variables var parts = [($asdf_dir'/bin/asdf' export-shell-version elvish $@args)] if (==s $parts[0] 'set-env') { set-env $parts[1] $parts[2] } elif (==s $parts[0] 'unset-env') { unset-env $parts[1] } } else { # forward other commands to asdf script $asdf_dir'/bin/asdf' $command $@args } } fn match {|argz @pats| var matched = $true if (!= (count $argz) (count $pats)) { set matched = $false } else { for i [(range (count $pats))] { var pat = '^'$pats[$i]'$' var arg = $argz[$i] if (not (re:match $pat $arg)) { set matched = $false break } } } put $matched } fn ls-shims { ls $asdf_data_dir'/shims' } fn ls-executables { # Print all executable files and links in path try { find $@paths '(' -type f -o -type l ')' -print 2>/dev/null | each {|p| try { if (test -x $p) { path:base $p } } catch { # don't fail if permission denied } } } catch { # silence default non-zero exit status } } fn ls-installed-versions {|plugin_name| asdf list $plugin_name | each {|version| put (re:replace '\s*(.*)\s*' '${1}' $version) } } fn ls-all-versions {|plugin_name| asdf list-all $plugin_name | each {|version| put (re:replace '\s*(.*)\s*' '${1}' $version) } } # Append ~/.asdf/bin and ~/.asdf/shims to PATH for path [ $asdf_dir'/bin' $asdf_data_dir'/shims' ] { if (not (has-value $paths $path)) { set paths = [ $path $@paths ] } } # Setup argument completions fn arg-completer {|@argz| set argz = $argz[1..-1] # strip 'asdf' and trailing empty string var num = (count $argz) if (== $num 0) { # list all subcommands find $asdf_dir'/lib/commands' -name 'command-*' | each {|cmd| put (re:replace '.*/command-(.*)\.bash' '${1}' $cmd) } put 'plugin' } else { if (match $argz 'current') { # asdf current asdf plugin-list } elif (match $argz 'env') { # asdf env ls-shims } elif (match $argz 'env' '.*') { # asdf env [util] ls-executables } elif (match $argz 'exec') { # asdf exec ls-shims } elif (match $argz 'global') { # asdf global asdf plugin-list } elif (match $argz 'global' '.*') { # asdf global ls-installed-versions $argz[-1] } elif (match $argz 'install') { # asdf install asdf plugin-list } elif (match $argz 'install' '.*') { # asdf install ls-all-versions $argz[-1] } elif (match $argz 'install' '.*' '.*') { # asdf install [--keep-download] put '--keep-download' } elif (match $argz 'latest') { # asdf latest asdf plugin-list } elif (match $argz 'latest' '.*') { # asdf latest [] ls-all-versions $argz[-1] } elif (match $argz 'list-all') { # asdf list all asdf plugin-list } elif (match $argz 'list-all' '.*') { # asdf list all [] ls-all-versions $argz[-1] } elif (match $argz 'list') { # asdf list asdf plugin-list } elif (match $argz 'list' '.*') { # asdf list [] ls-installed-versions $argz[-1] } elif (match $argz 'local') { # asdf local [-p|--parent] asdf plugin-list put '-p' put '--parent' } elif (match $argz 'local' '(-p|(--parent))') { # asdf local [-p|--parent] asdf plugin-list } elif (match $argz 'local' '.*') { # asdf local [-p|--parent] # asdf local ls-installed-versions $argz[-1] put '-p' put '--parent' } elif (match $argz 'local' '(-p|(--parent))' '.*') { # asdf local [-p|--parent] ls-installed-versions $argz[-1] } elif (match $argz 'local' '.*' '(-p|(--parent))') { # asdf local [-p|--parent] ls-installed-versions $argz[-2] } elif (match $argz 'local' '.*' '.*') { # asdf local [-p|--parent] put '-p' put '--parent' } elif (or (match $argz 'plugin-add') (match $argz 'plugin' 'add')) { # asdf plugin add asdf plugin-list-all | each {|line| put (re:replace '([^\s]+)\s+.*' '${1}' $line) } } elif (or (match $argz 'plugin-list') (match $argz 'plugin' 'list')) { # asdf plugin list put '--urls' put '--refs' put 'all' } elif (or (match $argz 'plugin-push') (match $argz 'plugin' 'push')) { # asdf plugin push asdf plugin-list } elif (or (match $argz 'plugin-remove') (match $argz 'plugin' 'remove')) { # asdf plugin remove asdf plugin-list } elif (and (>= (count $argz) 3) (match $argz[..3] 'plugin-test' '.*' '.*')) { # asdf plugin-test [--asdf-tool-version ] [--asdf-plugin-gitref ] [test-command*] put '--asdf-plugin-gitref' put '--asdf-tool-version' ls-executables ls-shims } elif (and (>= (count $argz) 4) (match $argz[..4] 'plugin' 'test' '.*' '.*')) { # asdf plugin test [--asdf-tool-version ] [--asdf-plugin-gitref ] [test-command*] put '--asdf-plugin-gitref' put '--asdf-tool-version' ls-executables ls-shims } elif (or (match $argz 'plugin-update') (match $argz 'plugin' 'update')) { # asdf plugin update asdf plugin-list put '--all' } elif (match $argz 'plugin') { # list plugin-* subcommands find $asdf_dir'/lib/commands' -name 'command-plugin-*' | each {|cmd| put (re:replace '.*/command-plugin-(.*)\.bash' '${1}' $cmd) } } elif (match $argz 'reshim') { # asdf reshim asdf plugin-list } elif (match $argz 'reshim' '.*') { # asdf reshim ls-installed-versions $argz[-1] } elif (match $argz 'shim-versions') { # asdf shim-versions ls-shims } elif (match $argz 'uninstall') { # asdf uninstall asdf plugin-list } elif (match $argz 'uninstall' '.*') { # asdf uninstall ls-installed-versions $argz[-1] } elif (match $argz 'update') { if (== $num 1) { # asdf update put '--head' } } elif (match $argz 'where') { # asdf where asdf plugin-list } elif (match $argz 'where' '.*') { # asdf where [] ls-installed-versions $argz[-1] } elif (match $argz 'which') { ls-shims } } } ================================================ FILE: asdf.fish ================================================ if test -z $ASDF_DIR set ASDF_DIR (builtin realpath --no-symlinks (dirname (status filename))) end set --export ASDF_DIR $ASDF_DIR set -l _asdf_bin "$ASDF_DIR/bin" if test -z $ASDF_DATA_DIR set _asdf_shims "$HOME/.asdf/shims" else 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 PATH if not contains $_asdf_bin $PATH set -gx --prepend PATH $_asdf_bin end if not contains $_asdf_shims $PATH set -gx --prepend PATH $_asdf_shims end set --erase _asdf_bin set --erase _asdf_shims # The asdf function is a 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 ================================================ FILE: asdf.nu ================================================ def --env configure-asdf [] { $env.ASDF_DIR = ( if ($env | get --ignore-errors ASDF_NU_DIR | is-empty) == false { $env.ASDF_NU_DIR } else if ($env | get --ignore-errors ASDF_DIR | is-empty) == false { $env.ASDF_DIR } else { print --stderr "asdf: Either ASDF_NU_DIR or ASDF_DIR must not be empty" return } ) let shims_dir = ( if ( $env | get --ignore-errors ASDF_DATA_DIR | is-empty ) { $env.HOME | path join '.asdf' } else { $env.ASDF_DATA_DIR } | path join 'shims' ) let asdf_bin_dir = ( $env.ASDF_DIR | path join 'bin' ) $env.PATH = ( $env.PATH | split row (char esep) | where { |p| $p != $shims_dir } | prepend $shims_dir ) $env.PATH = ( $env.PATH | split row (char esep) | where { |p| $p != $asdf_bin_dir } | prepend $asdf_bin_dir ) } configure-asdf ## Completions module asdf { def "complete asdf sub-commands" [] { [ "plugin", "list", "install", "uninstall", "current", "where", "which", "local", "global", "shell", "latest", "help", "exec", "env", "info", "reshim", "shim-version", "update" ] } def "complete asdf installed" [] { ^asdf plugin list | lines | each { |line| $line | str trim } } def "complete asdf plugin sub-commands" [] { [ "list", "list all", "add", "remove", "update" ] } def "complete asdf installed plugins" [] { ^asdf plugin list | lines | each { |line| $line | str trim } } def "complete asdf plugin versions all" [context: string] { let plugin = $context | str trim | split words | last ^asdf list all $plugin | lines | each { |line| $line | str trim } | prepend "latest" } def "complete asdf plugin versions installed" [context: string] { let plugin = $context | str trim | split words | last let versions = ^asdf list $plugin | lines | each { |line| $line | str trim } | each { |version| if ($version | str starts-with "*") {{value: ($version | str substring 1..), description: "current version"}} else {{value: $version, description: ""}} } let latest = ^asdf latest $plugin | str trim if ($versions | get value | any {|el| $el == $latest}) { $versions | prepend {value: "latest", description: $"alias to ($latest)"} } else { $versions } } # ASDF version manager export extern main [ subcommand?: string@"complete asdf sub-commands" ] # Manage plugins export extern "asdf plugin" [ subcommand?: string@"complete asdf plugin sub-commands" ] # List installed plugins export def "asdf plugin list" [ --urls # Show urls --refs # Show refs ] { let params = [ {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.+)' + ( $params | where enabled | get --ignore-errors template | str join '' | str trim ) let flags = ($params | where enabled | get --ignore-errors flag | default '' ) ^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|https).+)' let is_installed = { |it| $it.installed == '*' } ^asdf plugin list all | lines | parse -r $template | str trim | update installed $is_installed | sort-by name } # Add a plugin export extern "asdf plugin add" [ name: string # Name of the plugin git_url?: string # Git url of the plugin ] # Remove an installed plugin and their package versions export extern "asdf plugin remove" [ name: string@"complete asdf installed plugins" # Name of the plugin ] # Update a plugin export extern "asdf plugin update" [ name: string@"complete asdf installed plugins" # Name of the plugin git_ref?: string # Git ref to update the plugin ] # Update all plugins to the latest commit export extern "asdf plugin update --all" [] # install a package version export extern "asdf install" [ name?: string@"complete asdf installed plugins" # Name of the package version?: string@"complete asdf plugin versions all" # Version of the package or latest ] # Remove an installed package version export extern "asdf uninstall" [ name: string@"complete asdf installed" # Name of the package version: string@"complete asdf plugin versions installed" # Version of the package ] # Display current version export extern "asdf current" [ name?: string@"complete asdf installed" # Name of installed version of a package ] # Display path of an executable export extern "asdf which" [ command: string # Name of command ] # Display install path for an installed package version export extern "asdf where" [ name: string@"complete asdf installed" # Name of installed package version?: string@"complete asdf plugin versions installed" # Version of installed package ] # Set the package local version export extern "asdf local" [ name: string@"complete asdf installed" # Name of the package version?: string@"complete asdf plugin versions installed" # Version of the package or latest ] # Set the package global version export extern "asdf global" [ name: string@"complete asdf installed" # Name of the package version?: string@"complete asdf plugin versions installed" # Version of the package or latest ] # Set the package to version in the current shell export extern "asdf shell" [ name: string@"complete asdf installed" # Name of the package version?: string@"complete asdf plugin versions installed" # Version of the package or latest ] # Show latest stable version of a package export extern "asdf latest" [ name: string@"complete asdf installed" # Name of the package version?: string@"complete asdf plugin versions installed" # Filter latest stable version from this version ] # Show latest stable version for all installed packages export extern "asdf latest --all" [] # List installed package versions export extern "asdf list" [ name?: string@"complete asdf installed" # Name of the package version?: string@"complete asdf plugin versions installed" # Filter the version ] # List all available package versions export def "asdf list all" [ name: string@"complete asdf installed" # Name of the package version?: string@"complete asdf plugin versions installed"="" # Filter the version ] { ^asdf list all $name $version | lines | parse "{version}" | str trim } # Show documentation for plugin export extern "asdf help" [ name: string@"complete asdf installed" # Name of the plugin version?: string@"complete asdf plugin versions installed" # Version of the plugin ] # Execute a command shim for the current version export extern "asdf exec" [ command: string # Name of the command ...args: any # Arguments to pass to the command ] # Run util (default: env) inside the environment used for command shim execution export extern "asdf env" [ command?: string # Name of the command util?: string = 'env' # Name of util to run ] # Show information about OS, Shell and asdf Debug export extern "asdf info" [] # Recreate shims for version package export extern "asdf reshim" [ name?: string@"complete asdf installed" # Name of the package version?: string@"complete asdf plugin versions installed" # Version of the package ] # List the plugins and versions that provide a command export extern "asdf shim-version" [ command: string # Name of the command ] # Update asdf to the latest version on the stable branch export extern "asdf update" [] # Update asdf to the latest version on the main branch export extern "asdf update --head" [] } use asdf * ================================================ FILE: asdf.ps1 ================================================ $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 } } ================================================ FILE: asdf.sh ================================================ # 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 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 _new_path="$_new_path${_new_path:+:}$_part" done PATH=$_new_path unset -v _path _new_path _part fi fi 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 } ================================================ FILE: ballad-of-asdf.md ================================================ # Ballad of asdf > Once upon a time there was a programming language
> There were many versions of it
> So people wrote a version manager for it
> To switch between versions for projects
> Different, old, new. > Then there came more programming languages
> So there came more version managers
> And many commands for them > I installed a lot of them
> I learnt a lot of commands > Then I said, just one more version manager
> Which I will write instead > So, there came another version manager
> **asdf version manager** - > A version manager so extendable
> for which anyone can create a plugin
> To support their favourite language
> No more installing more version managers
> Or learning more commands --- *This was the mail I wrote to a few friends to tell them about the project. Thanks to [@roshanvid](https://twitter.com/roshanvid) for suggesting that this go into the readme* ================================================ FILE: bin/private/asdf-exec ================================================ #!/usr/bin/env bash # remove this asdf-exec file when releasing >=0.6.5 printf "asdf is self upgrading shims to new asdf exec ...\\n" asdf_dir="$(dirname "$(dirname "$(dirname "$0")")")" # shellcheck source=lib/utils.bash . "$asdf_dir/lib/utils.bash" rm "$(asdf_data_dir)"/shims/* "$asdf_dir"/bin/asdf reshim shim_name=$(basename "$2") printf "asdf: now running %s\\n" "$shim_name" exec "$shim_name" "${@:3}" ================================================ FILE: defaults ================================================ # See the docs for explanations: https://asdf-vm.com/manage/configuration.html legacy_version_file = no use_release_candidates = no always_keep_download = no plugin_repository_last_check_duration = 60 disable_plugin_short_name_repository = no concurrency = auto ================================================ FILE: docs/.gitattributes ================================================ ## GITATTRIBUTES FOR WEB PROJECTS # # These settings are for any web project. # # Details per file setting: # text These files should be normalized (i.e. convert CRLF to LF). # binary These files are binary and should be left untouched. # # Note that binary is a macro for -text -diff. ###################################################################### # Auto detect ## Handle line endings automatically for files detected as ## text and leave all files detected as binary untouched. ## This will handle all files NOT defined below. * text=auto # Source code *.bash text eol=lf *.bat text eol=crlf *.cmd text eol=crlf *.coffee text *.css text *.htm text diff=html *.html text diff=html *.inc text *.ini text *.js text *.json text *.jsx text *.less text *.ls text *.map text -diff *.od text *.onlydata text *.php text diff=php *.pl text *.ps1 text eol=crlf *.py text diff=python *.rb text diff=ruby *.sass text *.scm text *.scss text diff=css *.sh text eol=lf *.sql text *.styl text *.tag text *.ts text *.tsx text *.xml text *.xhtml text diff=html # Docker Dockerfile text # Documentation *.ipynb text *.markdown text *.md text *.mdwn text *.mdown text *.mkd text *.mkdn text *.mdtxt text *.mdtext text *.txt text AUTHORS text CHANGELOG text CHANGES text CONTRIBUTING text COPYING text copyright text *COPYRIGHT* text INSTALL text license text LICENSE text NEWS text readme text *README* text TODO text # Templates *.dot text *.ejs text *.haml text *.handlebars text *.hbs text *.hbt text *.jade text *.latte text *.mustache text *.njk text *.phtml text *.tmpl text *.tpl text *.twig text *.vue text # Configs *.cnf text *.conf text *.config text .editorconfig text .env text .gitattributes text .gitconfig text .htaccess text *.lock text -diff package-lock.json text -diff *.toml text *.yaml text *.yml text browserslist text Makefile text makefile text # Heroku Procfile text # Graphics *.ai binary *.bmp binary *.eps binary *.gif binary *.gifv binary *.ico binary *.jng binary *.jp2 binary *.jpg binary *.jpeg binary *.jpx binary *.jxr binary *.pdf binary *.png binary *.psb binary *.psd binary # SVG treated as an asset (binary) by default. *.svg text # If you want to treat it as binary, # use the following line instead. # *.svg binary *.svgz binary *.tif binary *.tiff binary *.wbmp binary *.webp binary # Audio *.kar binary *.m4a binary *.mid binary *.midi binary *.mp3 binary *.ogg binary *.ra binary # Video *.3gpp binary *.3gp binary *.as binary *.asf binary *.asx binary *.fla binary *.flv binary *.m4v binary *.mng binary *.mov binary *.mp4 binary *.mpeg binary *.mpg binary *.ogv binary *.swc binary *.swf binary *.webm binary # Archives *.7z binary *.gz binary *.jar binary *.rar binary *.tar binary *.zip binary # Fonts *.ttf binary *.eot binary *.otf binary *.woff binary *.woff2 binary # Executables *.exe binary *.pyc binary # RC files (like .babelrc or .eslintrc) *.*rc text # Ignore files (like .npmignore or .gitignore) *.*ignore text ================================================ FILE: docs/.gitignore ================================================ ### Custom ### # VitePress build output & cache directory .vitepress/cache .vitepress/dist # Created by https://www.toptal.com/developers/gitignore/api/node # Edit at https://www.toptal.com/developers/gitignore?templates=node ### Node ### # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* .pnpm-debug.log* # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage *.lcov # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # Snowpack dependency directory (https://snowpack.dev/) web_modules/ # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Microbundle cache .rpt2_cache/ .rts2_cache_cjs/ .rts2_cache_es/ .rts2_cache_umd/ # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env .env.test .env.production # parcel-bundler cache (https://parceljs.org/) .cache .parcel-cache # Next.js build output .next out # Nuxt.js build / generate output .nuxt dist # Gatsby files .cache/ # Comment in the public line in if your project uses Gatsby and not Next.js # https://nextjs.org/blog/next-9-1#public-directory-support # public # vuepress build output .vuepress/.cache .vuepress/.temp # Serverless directories .serverless/ # FuseBox cache .fusebox/ # DynamoDB Local files .dynamodb/ # TernJS port file .tern-port # Stores VSCode versions used for testing VSCode extensions .vscode-test # yarn v2 .yarn/cache .yarn/unplugged .yarn/build-state.yml .yarn/install-state.gz .pnp.* # End of https://www.toptal.com/developers/gitignore/api/node ================================================ FILE: docs/.prettierignore ================================================ # VitePress build output & cache directory .vitepress/cache .vitepress/dist node_modules package.json package-lock.json ================================================ FILE: docs/.tool-versions ================================================ nodejs 22.10.0 ================================================ FILE: docs/.vitepress/config.ts ================================================ import { defineConfig } from "vitepress"; import * as navbars from "./navbars"; import * as sidebars from "./sidebars"; // https://vitepress.dev/reference/site-config export default defineConfig({ title: "asdf", description: "Manage multiple runtime versions with a single CLI tool", lastUpdated: true, locales: { root: { label: "English", lang: "en-US", themeConfig: { nav: navbars.en, sidebar: sidebars.en, }, }, "ko-kr": { label: "한국어", lang: "ko-kr", themeConfig: { nav: navbars.ko_kr, sidebar: sidebars.ko_kr, }, }, "ja-jp": { label: "日本語", lang: "ja-jp", themeConfig: { nav: navbars.ja_jp, sidebar: sidebars.ja_jp, }, }, "pt-br": { label: "Brazilian Portuguese", lang: "pr-br", themeConfig: { nav: navbars.pt_br, sidebar: sidebars.pt_br, }, }, "zh-hans": { label: "简体中文", lang: "zh-hans", themeConfig: { nav: navbars.zh_hans, sidebar: sidebars.zh_hans, }, }, }, themeConfig: { // https://vitepress.dev/reference/default-theme-config search: { provider: "local", }, socialLinks: [ { icon: "github", link: "https://github.com/asdf-vm/asdf" }, // { icon: "twitter", link: "https://twitter.com/asdf_vm" }, ], }, }); ================================================ FILE: docs/.vitepress/navbars.ts ================================================ import path from "path"; import fs from "fs"; import process from "process"; export const getVersion = () => { const versionFilepath = path.join(__dirname, "../../.release-please-manifest.json"); try { const version = JSON.parse(fs.readFileSync(versionFilepath, "utf8"))['.']; console.log(`Found version ${version} from ${versionFilepath}`); return version; } catch (error) { console.error(`Failed to find version from file ${versionFilepath}`, error); process.exit(1); } }; const en = [ { text: "Guide", link: "/guide/getting-started" }, { text: "Reference", link: "/manage/configuration", }, { text: getVersion(), items: [ { text: "Changelog", link: "https://github.com/asdf-vm/asdf/blob/master/CHANGELOG.md", }, { text: "Contribute", link: "/contribute/core" }, ], }, ]; const ja_jp = [ { text: "ガイド", link: "/ja-jp/guide/getting-started" }, { text: "リファレンス", link: "/ja-jp/manage/configuration", }, { text: getVersion(), items: [ { text: "変更履歴", link: "https://github.com/asdf-vm/asdf/blob/master/CHANGELOG.md", }, { text: "コントリビューション", link: "/ja-jp/contribute/core" }, ], }, ]; const ko_kr = [ { text: "가이드", link: "/ko-kr/guide/getting-started" }, { text: "참고자료", link: "/ko-kr/manage/configuration", }, { text: getVersion(), items: [ { text: "변동사항", link: "https://github.com/asdf-vm/asdf/blob/master/CHANGELOG.md", }, { text: "기여하기", link: "/ko-kr/contribute/core" }, ], }, ]; const pt_br = [ { text: "Guia", link: "/pt-br/guide/getting-started" }, { text: "Referência", link: "/pt-br/manage/configuration", }, { text: getVersion(), items: [ { text: "Changelog", link: "https://github.com/asdf-vm/asdf/blob/master/CHANGELOG.md", }, { text: "Contribute", link: "/pt-br/contribute/core" }, ], }, ]; const zh_hans = [ { text: "指导", link: "/zh-hans/guide/getting-started" }, { text: "参考", link: "/zh-hans/manage/configuration", }, { text: getVersion(), items: [ { text: "Changelog", link: "https://github.com/asdf-vm/asdf/blob/master/CHANGELOG.md", }, { text: "如何贡献", link: "/zh-hans/contribute/core" }, ], }, ]; export { en, ko_kr, ja_jp, pt_br, zh_hans }; ================================================ FILE: docs/.vitepress/sidebars.ts ================================================ const en = [ { text: "Guide", collapsed: false, items: [ { text: "What is asdf?", link: "/guide/introduction" }, { text: "Getting Started", link: "/guide/getting-started" }, { text: "Getting Started (pre-0.16.0)", link: "/guide/getting-started-legacy" }, { text: "Upgrading to 0.16.0", link: "/guide/upgrading-to-v0-16" }, ], }, { text: "Usage", collapsed: false, items: [ { text: "Core", link: "/manage/core" }, { text: "Plugins", link: "/manage/plugins" }, { text: "Versions", link: "/manage/versions" }, ], }, { text: "Reference", collapsed: false, items: [ { text: "Configuration", link: "/manage/configuration" }, { text: "All Commands", link: "/manage/commands" }, { text: "Dependencies", link: "/manage/dependencies" }, { text: "Plugin Shortname Index", link: "https://github.com/asdf-vm/asdf-plugins", }, ], }, { text: "Plugins", collapsed: true, items: [ { text: "Authors", items: [ { text: "Create a Plugin", link: "/plugins/create" }, { text: "GitHub Plugin Template", link: "https://github.com/asdf-vm/asdf-plugin-template", }, ], }, { text: "First Party Plugins", items: [ { text: "Elixir", link: "https://github.com/asdf-vm/asdf-elixir", }, { text: "Erlang", link: "https://github.com/asdf-vm/asdf-erlang", }, { text: "Node.js", link: "https://github.com/asdf-vm/asdf-nodejs", }, { text: "Ruby", link: "https://github.com/asdf-vm/asdf-ruby", }, ], }, { text: "Community Plugins", items: [ { text: "asdf-community", link: "https://github.com/asdf-community", }, { text: "GitHub Topics Search", link: "https://github.com/topics/asdf-plugin", }, ], }, ], }, { text: "Questions", collapsed: true, items: [ { text: "FAQ", link: "/more/faq" }, { text: "GitHub Issues", link: "https://github.com/asdf-vm/asdf/issues", }, { text: "Stack Overflow Tag", link: "https://stackoverflow.com/questions/tagged/asdf-vm", }, ], }, { text: "Contribute", collapsed: true, items: [ { text: "Core asdf", link: "/contribute/core" }, { text: "Documentation", link: "/contribute/documentation" }, { text: "First-Party Plugins", link: "/contribute/first-party-plugins", }, { text: "GitHub Actions", link: "/contribute/github-actions" }, ], }, { text: "Community Projects", link: "/more/community-projects" }, { text: "Thanks", link: "/more/thanks" }, ]; const ko_kr = [ { text: "가이드", collapsed: false, items: [ { text: "asdf이란?", link: "/ko-kr/guide/introduction" }, { text: "시작하기", link: "/ko-kr/guide/getting-started" }, ], }, { text: "사용방법", collapsed: false, items: [ { text: "코어", link: "/ko-kr/manage/core" }, { text: "플러그인", link: "/ko-kr/manage/plugins" }, { text: "버전", link: "/ko-kr/manage/versions" }, ], }, { text: "참고자료", collapsed: false, items: [ { text: "설정", link: "/ko-kr/manage/configuration" }, { text: "모든 명령어", link: "/ko-kr/manage/commands" }, { text: "플러그인 Shortname 인덱스", link: "https://github.com/asdf-vm/asdf-plugins", }, ], }, { text: "플러그인", collapsed: true, items: [ { text: "저자", items: [ { text: "플러그인 만들기", link: "/ko-kr/plugins/create" }, { text: "GitHub 플러그인 템플릿", link: "https://github.com/asdf-vm/asdf-plugin-template", }, ], }, { text: "공식 플러그인", items: [ { text: "Elixir", link: "https://github.com/asdf-vm/asdf-elixir", }, { text: "Erlang", link: "https://github.com/asdf-vm/asdf-erlang", }, { text: "Node.js", link: "https://github.com/asdf-vm/asdf-nodejs", }, { text: "Ruby", link: "https://github.com/asdf-vm/asdf-ruby", }, ], }, { text: "커뮤니티 플러그인", items: [ { text: "asdf-community", link: "https://github.com/asdf-community", }, { text: "GitHub 토픽 검색", link: "https://github.com/topics/asdf-plugin", }, ], }, ], }, { text: "질문", collapsed: true, items: [ { text: "자주 묻는 질문", link: "/ko-kr/more/faq" }, { text: "GitHub 이슈", link: "https://github.com/asdf-vm/asdf/issues", }, { text: "Stack Overflow 태그", link: "https://stackoverflow.com/questions/tagged/asdf-vm", }, ], }, { text: "기여하기", collapsed: true, items: [ { text: "코어 asdf", link: "/ko-kr/contribute/core" }, { text: "문서", link: "/ko-kr/contribute/documentation" }, { text: "공식 플러그인", link: "/ko-kr/contribute/first-party-plugins", }, { text: "GitHub Actions", link: "/ko-kr/contribute/github-actions" }, ], }, { text: "커뮤니티 프로젝트", link: "/ko-kr/more/community-projects" }, { text: "감사인사", link: "/ko-kr/more/thanks" }, ]; const ja_jp = [ { text: "ガイド", collapsed: false, items: [ { text: "asdfってなに?", link: "/ja-jp/guide/introduction" }, { text: "はじめよう", link: "/ja-jp/guide/getting-started" }, { text: "はじめよう (0.16.0以前)", link: "/ja-jp/guide/getting-started-legacy" }, { text: "0.16.0にアップグレードする", link: "/ja-jp/guide/upgrading-to-v0-16" }, ], }, { text: "使い方", collapsed: false, items: [ { text: "コア", link: "/ja-jp/manage/core" }, { text: "プラグイン", link: "/ja-jp/manage/plugins" }, { text: "バージョン", link: "/ja-jp/manage/versions" }, ], }, { text: "リファレンス", collapsed: false, items: [ { text: "構成設定", link: "/ja-jp/manage/configuration" }, { text: "すべてのコマンド", link: "/ja-jp/manage/commands" }, { text: "プラグインショートネームの一覧", link: "https://github.com/asdf-vm/asdf-plugins", }, ], }, { text: "プラグイン", collapsed: true, items: [ { text: "開発者向け", items: [ { text: "プラグインの作成", link: "/ja-jp/plugins/create" }, { text: "GitHubプラグインテンプレート", link: "https://github.com/asdf-vm/asdf-plugin-template", }, ], }, { text: "公式プラグイン", items: [ { text: "Elixir", link: "https://github.com/asdf-vm/asdf-elixir", }, { text: "Erlang", link: "https://github.com/asdf-vm/asdf-erlang", }, { text: "Node.js", link: "https://github.com/asdf-vm/asdf-nodejs", }, { text: "Ruby", link: "https://github.com/asdf-vm/asdf-ruby", }, ], }, { text: "コミュニティプラグイン", items: [ { text: "asdf-community", link: "https://github.com/asdf-community", }, { text: "GitHubトピック検索", link: "https://github.com/topics/asdf-plugin", }, ], }, ], }, { text: "困ったときは", collapsed: true, items: [ { text: "FAQ", link: "/ja-jp/more/faq" }, { text: "GitHub イシュー", link: "https://github.com/asdf-vm/asdf/issues", }, { text: "Stack Overflow タグ", link: "https://stackoverflow.com/questions/tagged/asdf-vm", }, ], }, { text: "コントリビューション", collapsed: true, items: [ { text: "asdf コア", link: "/ja-jp/contribute/core" }, { text: "ドキュメント", link: "/ja-jp/contribute/documentation" }, { text: "公式プラグイン", link: "/ja-jp/contribute/first-party-plugins", }, { text: "GitHub Actions", link: "/ja-jp/contribute/github-actions" }, ], }, { text: "コミュニティプロジェクト", link: "/ja-jp/more/community-projects" }, { text: "謝辞", link: "/ja-jp/more/thanks" }, ]; const pt_br = [ { text: "Guia", collapsed: false, items: [ { text: "O que é asdf?", link: "/pt-br/guide/introduction" }, { text: "Começar", link: "/pt-br/guide/getting-started" }, ], }, { text: "Uso", collapsed: false, items: [ { text: "Essencial", link: "/pt-br/manage/core" }, { text: "Plugins", link: "/pt-br/manage/plugins" }, { text: "Versões", link: "/pt-br/manage/versions" }, ], }, { text: "Referência", collapsed: false, items: [ { text: "Configuração", link: "/pt-br/manage/configuration" }, { text: "Todos os comandos", link: "/pt-br/manage/commands" }, { text: "Plugin Shortname Index", link: "https://github.com/asdf-vm/asdf-plugins", }, ], }, { text: "Plugins", collapsed: true, items: [ { text: "Autoria", items: [ { text: "Criar um plug-in", link: "/pt-br/plugins/create" }, { text: "GitHub Plugin Template", link: "https://github.com/asdf-vm/asdf-plugin-template", }, ], }, { text: "Plug-ins Próprios", items: [ { text: "Elixir", link: "https://github.com/asdf-vm/asdf-elixir", }, { text: "Erlang", link: "https://github.com/asdf-vm/asdf-erlang", }, { text: "Node.js", link: "https://github.com/asdf-vm/asdf-nodejs", }, { text: "Ruby", link: "https://github.com/asdf-vm/asdf-ruby", }, ], }, { text: "Plug-ins da Comunidade", items: [ { text: "asdf-community", link: "https://github.com/asdf-community", }, { text: "GitHub Topics Search", link: "https://github.com/topics/asdf-plugin", }, ], }, ], }, { text: "Questões", collapsed: true, items: [ { text: "Perguntas Frequentes", link: "/pt-br/more/faq" }, { text: "GitHub Issues", link: "https://github.com/asdf-vm/asdf/issues", }, { text: "Stack Overflow Tag", link: "https://stackoverflow.com/questions/tagged/asdf-vm", }, ], }, { text: "Contribute", collapsed: true, items: [ { text: "Essencial asdf", link: "/pt-br/contribute/core" }, { text: "Documentação", link: "/pt-br/contribute/documentation" }, { text: "Plug-ins Próprios", link: "/pt-br/contribute/first-party-plugins", }, { text: "GitHub Actions", link: "/pt-br/contribute/github-actions" }, ], }, { text: "Projetos Comunitários", link: "/pt-br/more/community-projects" }, { text: "Créditos", link: "/pt-br/more/thanks" }, ]; const zh_hans = [ { text: "指导", collapsed: false, items: [ { text: "什么是 asdf?", link: "/zh-hans/guide/introduction" }, { text: "快速入门", link: "/zh-hans/guide/getting-started" }, { text: "快速入门 (0.16.0 之前)", link: "/zh-hans/guide/getting-started-legacy", }, { text: "升级到 0.16.0", link: "/zh-hans/guide/upgrading-to-v0-16" }, ], }, { text: "用法", collapsed: false, items: [ { text: "核心", link: "/zh-hans/manage/core" }, { text: "插件", link: "/zh-hans/manage/plugins" }, { text: "版本", link: "/zh-hans/manage/versions" }, ], }, { text: "参考", collapsed: false, items: [ { text: "配置", link: "/zh-hans/manage/configuration" }, { text: "所有命令", link: "/zh-hans/manage/commands" }, { text: "依赖", link: "/zh-hans/manage/dependencies" }, { text: "插件缩写索引", link: "https://github.com/asdf-vm/asdf-plugins", }, ], }, { text: "插件", collapsed: true, items: [ { text: "成为作者", items: [ { text: "创建插件", link: "/zh-hans/plugins/create" }, { text: "GitHub 插件模板", link: "https://github.com/asdf-vm/asdf-plugin-template", }, ], }, { text: "官方插件", items: [ { text: "Elixir", link: "https://github.com/asdf-vm/asdf-elixir", }, { text: "Erlang", link: "https://github.com/asdf-vm/asdf-erlang", }, { text: "Node.js", link: "https://github.com/asdf-vm/asdf-nodejs", }, { text: "Ruby", link: "https://github.com/asdf-vm/asdf-ruby", }, ], }, { text: "社区插件", items: [ { text: "asdf-community", link: "https://github.com/asdf-community", }, { text: "GitHub 主题搜索", link: "https://github.com/topics/asdf-plugin", }, ], }, ], }, { text: "问题", collapsed: true, items: [ { text: "经常问的问题", link: "/zh-hans/more/faq" }, { text: "GitHub Issues", link: "https://github.com/asdf-vm/asdf/issues", }, { text: "Stack Overflow Tag", link: "https://stackoverflow.com/questions/tagged/asdf-vm", }, ], }, { text: "如何贡献", collapsed: true, items: [ { text: "核心", link: "/zh-hans/contribute/core" }, { text: "文档", link: "/zh-hans/contribute/documentation" }, { text: "官方插件", link: "/zh-hans/contribute/first-party-plugins", }, { text: "GitHub Actions", link: "/zh-hans/contribute/github-actions" }, ], }, { text: "社区项目", link: "/zh-hans/more/community-projects" }, { text: "致谢", link: "/zh-hans/more/thanks" }, ]; export { en, ko_kr, ja_jp, pt_br, zh_hans }; ================================================ FILE: docs/.vitepress/theme/custom.css ================================================ :root { --vp-c-brand-1: #b744b8; --vp-c-brand-2: #a379c9; /* TODO: make brand-2 and brand-3 different & set brand-soft */ --vp-c-brand-3: #a379c9; /* --vp-c-brand-soft: #fae3ff; */ } ================================================ FILE: docs/.vitepress/theme/index.ts ================================================ import DefaultTheme from "vitepress/theme"; import "./custom.css"; export default DefaultTheme; ================================================ FILE: docs/CNAME ================================================ asdf-vm.com ================================================ FILE: docs/contribute/core.md ================================================ # asdf `asdf` core contribution guide. ## Initial Setup Fork `asdf` on GitHub and/or Git clone the default branch: ```shell # clone your fork git clone https://github.com//asdf.git # or clone asdf git clone https://github.com/asdf-vm/asdf.git ``` The tools for core development are in this repo's `.tool-versions`. If you wish to manage with `asdf` itself, add the plugins: ```shell asdf plugin add bats https://github.com/timgluz/asdf-bats.git asdf plugin add shellcheck https://github.com/luizm/asdf-shellcheck.git asdf plugin add shfmt https://github.com/luizm/asdf-shfmt.git ``` Install the versions to develop `asdf` with: ```shell asdf install ``` It _may_ be useful to not use `asdf` to manage the tools during development on your local machine as you may need to break functionality which would then break your dev tooling. Here's the raw list of tools: - [bats-core](https://github.com/bats-core/bats-core): Bash Automated Testing System, for unit testing Bash or POSIX compliant scripts. - [shellcheck](https://github.com/koalaman/shellcheck): Static analysis tool for shell scripts. - [shfmt](https://github.com/mvdan/sh): A shell parser, formatter, and interpreter with bash support; includes shfmt ## Development If you want to try out your changes without making change to your installed `asdf`, you can set the `$ASDF_DIR` variable to the path where you cloned the repository, and temporarily prepend the `bin` and `shims` directory of the directory to your path. 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 # Lint ./scripts/lint.bash --check # Fix & Format ./scripts/lint.bash --fix # Test: all tests ./scripts/test.bash # Test: for specific command bats test/list_commands.bash ``` ::: tip **Add tests!** - Tests are **required** for new features and speed up review of bug fixes. Please cover new code paths before you create a Pull Request. See [bats-core documentation](https://bats-core.readthedocs.io/en/stable/index.html) ::: ### Gitignore The following is the `.gitignore` file in the `asdf-vm/asdf` repository. We ignore project-specific files. Files specific to your OS, tools or workflows should be ignored in your global `.gitignore` configuration, [see here](http://stratus3d.com/blog/2018/06/03/stop-excluding-editor-temp-files-in-gitignore/) for more details. <<< @/../.gitignore ### `.git-blame-ignore-revs` `asdf` uses a `.git-blame-ignore-revs` to reduce noise when running a blame. See the [git blame documentation](https://git-scm.com/docs/git-blame) for more information. Use the file with `git blame` like so: ```sh git blame --ignore-revs-file .git-blame-ignore-revs ./test/install_command.bats ``` Optionally, configure to use the file on every invocation of `blame` without manually supplying it: ```sh git config blame.ignoreRevsFile .git-blame-ignore-revs ``` It is possible to configure IDEs to use this file. For example, when using VSCode (with [GitLens](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens)), write the following to `.vscode/settings.json`: ```json { "gitlens.advanced.blame.customArguments": [ "--ignore-revs-file", ".git-blame-ignore-revs" ] } ``` ## Bats Testing Execute tests locally with: ```shell ./scripts/test.bash ``` Before writing tests **please read**: - existing tests in `test/` - [bats-core documentation](https://bats-core.readthedocs.io/en/stable/index.html) - existing Bats settings used in `scripts/test.bash` ### Bats Tips Bats debugging can be difficult at times. Using the TAP output with `-t` flag will enable you to print outputs with the special file descriptor `>&3` during test execution, simplifying debugging. As an example: ```shell # test/some_tests.bats printf "%s\n" "Will not be printed during bats test/some_tests.bats" printf "%s\n" "Will be printed during bats -t test/some_tests.bats" >&3 ``` This is further documented in bats-core [Printing to the Terminal](https://bats-core.readthedocs.io/en/stable/writing-tests.html#printing-to-the-terminal). ## Pull Requests, Releases & Conventional Commits `asdf` is using an automated release tool called [Release Please](https://github.com/googleapis/release-please) to automatically bump the [SemVer](https://semver.org/) version and generate the [Changelog](https://github.com/asdf-vm/asdf/blob/master/CHANGELOG.md). This information is determined by reading the commit history since the last release. [Conventional Commit messages](https://www.conventionalcommits.org/) define the format of the Pull Request Title which becomes the commit message format on the default branch. This is enforced with GitHub Action [`amannn/action-semantic-pull-request`](https://github.com/amannn/action-semantic-pull-request). Conventional Commit follows this format: ``` [optional scope][optional !]: fix: some fix feat: a new feature docs: some documentation update docs(website): some change for the website feat!: feature with breaking change ``` The full list of `` are: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`. - `!`: indicates a breaking change - `fix`: will create a new SemVer `patch` - `feat`: will create a new SemVer `minor` - `!`: will create a new SemVer `major` The Pull Request Title must follow this format. ::: tip Use Conventional Commit message format for your Pull Request Title. ::: ## Docker Images The [asdf-alpine](https://github.com/vic/asdf-alpine) and [asdf-ubuntu](https://github.com/vic/asdf-ubuntu) projects are an ongoing effort to provide Dockerized images of some asdf tools. You can use these docker images as base for your development servers, or for running your production apps. ================================================ FILE: docs/contribute/documentation.md ================================================ # Docs & Site Documentation & site contribution guide. ## Initial Setup Fork `asdf` on GitHub and/or Git clone the default branch: ```shell # clone your fork git clone https://github.com//asdf.git # or clone asdf git clone https://github.com/asdf-vm/asdf.git ``` The tools for Docs site development are managed with `asdf` in the `docs/.tool-versions`. Add the plugins with: ```shell asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs ``` Install the tool version(s) with: ```shell asdf install ``` - [Node.js](https://nodejs.org): JavaScript runtime built on Chrome's V8 JavaScript engine. Install Node.js dependencies from `docs/package.json`: ```shell npm install ``` ## Development [VitePress (v2)](https://vitepress.dev/) is the Static Site Generator (SSG) we use to build the asdf documentation site. It was chosen to replace [Docsify.js](https://docsify.js.org/) and subsequently VuePress as we would like to support an HTML only fallback when users do not have JavaScript available or enabled. This was not possible with Docsify & VitePress quickly supplanted VuePress. Other than this, the feature-set is largely the same, with the focus on writing Markdown files with minimal configuration. `package.json` contains the scripts required for development: @[code json{3-5}](../package.json) To start the local development server: ```shell npm run dev ``` Format the code before committing: ```shell npm run format ``` ## Pull Requests, Releases & Conventional Commits `asdf` is using an automated release pipeline which relies on Conventional Commits in PR titles. Detailed documentation found in the [core contribution guide](./core.md). When creating a PR for documentation changes please make the PR title with the Conventional Commit type `docs` in the format `docs: `. ## Vitepress Configuration of the site is contained within a few TypeScript files with JS Objects used to represent the config. They are: - `docs/.vitepress/config.js`: the root config file for the site. Read the [VitePress documentation](https://vitepress.dev/reference/site-config) for it's spec. To simplify the root config, the larger JS Objects representing the _navbar_ and _sidebar_ configuration have been extracted and separated by their locale. See both in: - `docs/.vitepress/navbars.js` - `docs/.vitepress/sidebars.js` With the official documentation for these configs living in the [Default Theme Reference](https://vitepress.dev/reference/default-theme-config). ## I18n VitePress has first-class support for internationalization. The root config `docs/.vitepress/config.js` defines the supported locales with their URL, title in the selection dropdown menu and navbar/sidebar configs references. The navbar/sidebar configs are captured in the aforementioned config files, separated by locale and exported individually. The markdown content for each locale must fall under a folder with the same name as the keys for `locales` in the root config. That is: ```js // docs/.vitepress/config.js export default defineConfig({ ... locales: { root: { label: "English", lang: "en-US", themeConfig: { nav: navbars.en, sidebar: sidebars.en, }, }, "pt-br": { label: "Brazilian Portuguese", lang: "pr-br", themeConfig: { nav: navbars.pt_br, sidebar: sidebars.pt_br, }, }, "zh-hans": { label: "简体中文", lang: "zh-hans", themeConfig: { nav: navbars.zh_hans, sidebar: sidebars.zh_hans, }, }, }, }) ``` `/pt-BR/` will require the same set of markdown files located under `docs/pt-BR/`, like so: ```shell docs ├─ README.md ├─ foo.md ├─ nested │ └─ README.md └─ pt-BR ├─ README.md ├─ foo.md └─ nested └─ README.md ``` The [official VitePress i18n documentation](https://vitepress.dev/guide/i18n) goes into more detail. ================================================ FILE: docs/contribute/first-party-plugins.md ================================================ # First-Party Plugins The asdf core team has authored some plugins relevant to their daily work life. Help is always welcome in maintaining and improving these plugins. See the associated repo for each linked below: - [Elixir](https://github.com/asdf-vm/asdf-elixir) - [Erlang](https://github.com/asdf-vm/asdf-erlang) - [Node.js](https://github.com/asdf-vm/asdf-nodejs) - [Ruby](https://github.com/asdf-vm/asdf-ruby) For community plugins, see: - [`asdf-community` organisation](https://github.com/asdf-community): A collaborative, community-driven project for long-term maintenance of `asdf` plugins. - [`asdf-plugins` shortname repo](https://github.com/asdf-vm/asdf-plugins): Short-name list used by `asdf` core to lookup popular `asdf` plugins. - [GitHub `asdf-plugin` topic search](https://github.com/topics/asdf-plugin) ================================================ FILE: docs/contribute/github-actions.md ================================================ # GitHub Actions Thanks for your interest, please see the [asdf actions repo](https://github.com/asdf-vm/actions) for their existing Issues, conversations and Contributing Guidelines. ================================================ FILE: docs/guide/getting-started-legacy.md ================================================ # Getting Started `asdf` installation involves: 1. Installing dependencies 2. Downloading `asdf` core 3. Installing `asdf` 4. Installing a plugin for each tool/runtime you wish to manage 5. Installing a version of the tool/runtime 6. Setting global and project versions via `.tool-versions` config files ## 1. Install Dependencies asdf primarily requires `git` & `curl`. Here is a _non-exhaustive_ list of commands to run for _your_ package manager (some might automatically install these tools in later steps). | OS | Package Manager | Command | | ----- | --------------- | ---------------------------------- | | linux | Aptitude | `apt install curl git` | | linux | DNF | `dnf install curl git` | | linux | Pacman | `pacman -S curl git` | | linux | Zypper | `zypper install curl git` | | macOS | Homebrew | `brew install coreutils curl git` | | macOS | Spack | `spack install coreutils curl git` | ::: tip Note `sudo` may be required depending on your system configuration. ::: ## 2. Download asdf ### Official Download ```shell git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.15.0 ``` ### Community Supported Download Methods We highly recommend using the official `git` method. | Method | Command | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Homebrew | `brew install asdf` | | Pacman | `git clone https://aur.archlinux.org/asdf-vm.git && cd asdf-vm && makepkg -si` or use your preferred [AUR helper](https://wiki.archlinux.org/index.php/AUR_helpers) | ## 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. **macOS users, be sure to read the warning about `path_helper` at the end of this section.** ::: details Bash & Git Add the following to `~/.bashrc`: ```shell . "$HOME/.asdf/asdf.sh" ``` Completions must be configured by adding the following to your `.bashrc`: ```shell . "$HOME/.asdf/completions/asdf.bash" ``` ::: ::: details Bash & Git (macOS) If using **macOS Catalina or newer**, the default shell has changed to **ZSH**. Unless changing back to Bash, follow the ZSH instructions. Add the following to `~/.bash_profile`: ```shell . "$HOME/.asdf/asdf.sh" ``` Completions must be configured manually with the following entry in your `.bash_profile`: ```shell . "$HOME/.asdf/completions/asdf.bash" ``` ::: ::: details Bash & Homebrew Add `asdf.sh` to your `~/.bashrc` with: ```shell echo -e "\n. \"$(brew --prefix asdf)/libexec/asdf.sh\"" >> ~/.bashrc ``` Completions will need to be [configured as per Homebrew's instructions](https://docs.brew.sh/Shell-Completion#configuring-completions-in-bash) or with the following: ```shell echo -e "\n. \"$(brew --prefix asdf)/etc/bash_completion.d/asdf.bash\"" >> ~/.bashrc ``` ::: ::: details Bash & Homebrew (macOS) If using **macOS Catalina or newer**, the default shell has changed to **ZSH**. Unless changing back to Bash, follow the ZSH instructions. Add `asdf.sh` to your `~/.bash_profile` with: ```shell echo -e "\n. \"$(brew --prefix asdf)/libexec/asdf.sh\"" >> ~/.bash_profile ``` Completions will need to be [configured as per Homebrew's instructions](https://docs.brew.sh/Shell-Completion#configuring-completions-in-bash) or with the following: ```shell echo -e "\n. \"$(brew --prefix asdf)/etc/bash_completion.d/asdf.bash\"" >> ~/.bash_profile ``` ::: ::: details Bash & Pacman Add the following to `~/.bashrc`: ```shell . /opt/asdf-vm/asdf.sh ``` [`bash-completion`](https://wiki.archlinux.org/title/bash#Common_programs_and_options) needs to be installed for the completions to work. ::: ::: details Fish & Git Add the following to `~/.config/fish/config.fish`: ```shell source ~/.asdf/asdf.fish ``` Completions must be configured manually with the following command: ```shell mkdir -p ~/.config/fish/completions; and ln -s ~/.asdf/completions/asdf.fish ~/.config/fish/completions ``` ::: ::: details Fish & Homebrew Add `asdf.fish` to your `~/.config/fish/config.fish` with: ```shell echo -e "\nsource "(brew --prefix asdf)"/libexec/asdf.fish" >> ~/.config/fish/config.fish ``` Completions are [handled by Homebrew for the Fish shell](https://docs.brew.sh/Shell-Completion#configuring-completions-in-fish). Friendly! ::: ::: details Fish & Pacman Add the following to `~/.config/fish/config.fish`: ```shell source /opt/asdf-vm/asdf.fish ``` Completions are automatically configured on installation by the AUR package. ::: ::: details Elvish & Git Add `asdf.elv` to your `~/.config/elvish/rc.elv` with: ```shell mkdir -p ~/.config/elvish/lib; ln -s ~/.asdf/asdf.elv ~/.config/elvish/lib/asdf.elv echo "\n"'use asdf _asdf; var asdf~ = $_asdf:asdf~' >> ~/.config/elvish/rc.elv echo "\n"'set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~' >> ~/.config/elvish/rc.elv ``` Completions are automatically configured. ::: ::: details Elvish & Homebrew Add `asdf.elv` to your `~/.config/elvish/rc.elv` with: ```shell mkdir -p ~/.config/elvish/lib; ln -s (brew --prefix asdf)/libexec/asdf.elv ~/.config/elvish/lib/asdf.elv echo "\n"'use asdf _asdf; var asdf~ = $_asdf:asdf~' >> ~/.config/elvish/rc.elv echo "\n"'set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~' >> ~/.config/elvish/rc.elv ``` Completions are automatically configured. ::: ::: details Elvish & Pacman Add `asdf.elv` to your `~/.config/elvish/rc.elv` with: ```shell mkdir -p ~/.config/elvish/lib; ln -s /opt/asdf-vm/asdf.elv ~/.config/elvish/lib/asdf.elv echo "\n"'use asdf _asdf; var asdf~ = $_asdf:asdf~' >> ~/.config/elvish/rc.elv echo "\n"'set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~' >> ~/.config/elvish/rc.elv ``` Completions are automatically configured. ::: ::: details ZSH & Git Add the following to `~/.zshrc`: ```shell . "$HOME/.asdf/asdf.sh" ``` **OR** use a ZSH Framework plugin like [asdf for oh-my-zsh](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/asdf) which will source this script and setup completions. Completions are configured by either a ZSH Framework `asdf` plugin or by adding the following to your `.zshrc`: ```shell # append completions to fpath fpath=(${ASDF_DIR}/completions $fpath) # initialise completions with ZSH's compinit autoload -Uz compinit && compinit ``` - if you are using a custom `compinit` setup, ensure `compinit` is below your sourcing of `asdf.sh` - if you are using a custom `compinit` setup with a ZSH Framework, ensure `compinit` is below your sourcing of the framework ::: ::: details ZSH & Homebrew Add `asdf.sh` to your `~/.zshrc` with: ```shell echo -e "\n. $(brew --prefix asdf)/libexec/asdf.sh" >> ${ZDOTDIR:-~}/.zshrc ``` **OR** use a ZSH Framework plugin like [asdf for oh-my-zsh](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/asdf) which will source this script and setup completions. Completions are configured by either a ZSH Framework `asdf` or will need to be [configured as per Homebrew's instructions](https://docs.brew.sh/Shell-Completion#configuring-completions-in-zsh). If you are using a ZSH Framework the associated plugin for asdf may need to be updated to use the new ZSH completions properly via `fpath`. The Oh-My-ZSH asdf plugin is yet to be updated, see [ohmyzsh/ohmyzsh#8837](https://github.com/ohmyzsh/ohmyzsh/pull/8837). ::: ::: details ZSH & Pacman Add the following to `~/.zshrc`: ```shell . /opt/asdf-vm/asdf.sh ``` 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 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: ```shell "\n$env.ASDF_DIR = ($env.HOME | path join '.asdf')\n source " + ($env.HOME | path join '.asdf/asdf.nu') | save --append $nu.config-path ``` Completions are automatically configured ::: ::: details Nushell & Homebrew Add `asdf.nu` to your `~/.config/nushell/config.nu` with: ```shell "\n$env.ASDF_DIR = (brew --prefix asdf | str trim | into string | path join 'libexec')\n source " + (brew --prefix asdf | str trim | into string | path join 'libexec/asdf.nu') | save --append $nu.config-path ``` Completions are automatically configured ::: ::: details Nushell & Pacman Add `asdf.nu` to your `~/.config/nushell/config.nu` with: ```shell "\n$env.ASDF_DIR = '/opt/asdf-vm/'\n source /opt/asdf-vm/asdf.nu" | save --append $nu.config-path ``` 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 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 forcibly 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! This completes the installation of the `asdf` core :tada: `asdf` is only useful once you install a **plugin**, install a **tool** and manage its **versions**. Continue the guide below to learn how to do this. ## 4. Install a Plugin For demonstration purposes we will install & set [Node.js](https://nodejs.org/) via the [`asdf-nodejs`](https://github.com/asdf-vm/asdf-nodejs/) plugin. ### Plugin Dependencies Each plugin has dependencies so we need to check the plugin repo where they should be listed. For `asdf-nodejs` they are: | OS | Dependency Installation | | ------------------------------ | --------------------------------------- | | Debian | `apt-get install dirmngr gpg curl gawk` | | CentOS/ Rocky Linux/ AlmaLinux | `yum install gnupg2 curl gawk` | | macOS | `brew install gpg gawk` | We should install dependencies first as some Plugins have post-install hooks. ### Install the Plugin ```shell asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git ``` ## 5. Install a Version Now we have a plugin for Node.js we can install a version of the tool. We can see which versions are available with `asdf list all nodejs` or a subset of versions with `asdf list all nodejs 14`. We will just install the `latest` available version: ```shell asdf install nodejs latest ``` ::: tip Note `asdf` enforces exact versions. `latest` is a helper throughout `asdf` that will resolve to the actual version number at the time of execution. ::: ## 6. Set a Version `asdf` performs a version lookup of a tool in all `.tool-versions` files from the current working directory up to the `$HOME` directory. The lookup occurs just-in-time when you execute a tool that `asdf` manages. ::: warning Without a version listed for a tool execution of the tool will **error**. `asdf current` will show you the tool & version resolution, or absence of, from your current directory so you can observe which tools will fail to execute. ::: ### Global Global defaults are managed in `$HOME/.tool-versions`. Set a global version with: ```shell asdf global nodejs latest ``` `$HOME/.tool-versions` will then look like: ``` nodejs 16.5.0 ``` Some OSs already have tools installed that are managed by the system and not `asdf`, `python` is a common example. You need to tell `asdf` to pass the management back to the system. The [Versions reference section](/manage/versions.md) will guide you. ### Local Local versions are defined in the `$PWD/.tool-versions` file (your current working directory). Usually, this will be the Git repository for a project. When in your desired directory execute: ```shell asdf local nodejs latest ``` `$PWD/.tool-versions` will then look like: ``` nodejs 16.5.0 ``` ### Using Existing Tool Version Files `asdf` supports the migration from existing version files from other version managers. Eg: `.ruby-version` for the case of `rbenv`. This is supported on a per-plugin basis. [`asdf-nodejs`](https://github.com/asdf-vm/asdf-nodejs/) supports this via both `.nvmrc` and `.node-version` files. To enable this, add the following to your `asdf` configuration file `$HOME/.asdfrc`: ``` legacy_version_file = yes ``` See the [configuration](/manage/configuration.md) reference page for more config options. ## Guide Complete! That completes the Getting Started guide for `asdf` :tada: You can now manage `nodejs` versions for your project. Follow similar steps for each type of tool in your project! `asdf` has many more commands to become familiar with, you can see them all by running `asdf --help` or `asdf`. The core of the commands are broken into three categories: - [core `asdf`](/manage/core.md) - [plugins](/manage/plugins.md) - [versions (of tools)](/manage/versions.md) ================================================ FILE: docs/guide/getting-started.md ================================================ # Getting Started ## 1. Install asdf asdf can be installed in several different ways: ::: details With Package Manager - **Recommended** | Package Manager | Command | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Homebrew | `brew install asdf` | | Zypper | `zypper install asdf` | | Pacman | `git clone https://aur.archlinux.org/asdf-vm.git && cd asdf-vm && makepkg -si` or use your preferred [AUR helper](https://wiki.archlinux.org/index.php/AUR_helpers) | ::: :::: details Download Pre-Compiled Binary - **Easy** ##### Install asdf 1. Visit https://github.com/asdf-vm/asdf/releases and download the appropriate archive for your operating system/architecture combination. 2. Extract the `asdf` binary in the archive into a directory on your `$PATH`. 3. Verify `asdf` is on your shell's `$PATH` by running `type -a asdf`. The directory you placed the `asdf` binary in should be listed on the first line of the output from `type`. If it is not that means step #2 was not completed correctly. :::: :::: details With `go install` ##### Install asdf 1. [Install Go](https://go.dev/doc/install) 2. Run `go install github.com/asdf-vm/asdf/cmd/asdf@v0.18.1` :::: :::: details Build from Source ##### Install asdf 1. Clone the asdf repository: ```shell git clone https://github.com/asdf-vm/asdf.git --branch v0.18.1 ``` 2. Run `make` 3. Copy the `asdf` binary into a directory on your `$PATH`. 4. Verify `asdf` is on your shell's `$PATH` by running `type -a asdf`. The directory you placed the `asdf` binary in should be listed on the first line of the output from `type`. If it is not that means step #3 was not completed correctly. :::: ## 2. Configure asdf ::: tip Note Most users **DO NOT** need to customize the location that asdf writes plugin, install, and shim data to. However, if `$HOME/.asdf` isn't the directory you want asdf writing to, you can change it. Specify the directory by exporting a variable named `ASDF_DATA_DIR` in your shell's RC file. ::: 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. ::: details Bash **macOS Catalina or newer**: The default shell has changed to **ZSH**. Unless changing back to Bash, follow the ZSH instructions. **Pacman**: [`bash-completion`](https://wiki.archlinux.org/title/bash#Common_programs_and_options) needs to be installed for the completions to work. ##### Add shims directory to path (required) Add the following to `~/.bash_profile`: ```shell export PATH="${ASDF_DATA_DIR:-$HOME/.asdf}/shims:$PATH" ``` ###### Custom data directory (optional) Add the following to `~/.bash_profile` above the line you added above: ```shell export ASDF_DATA_DIR="/your/custom/data/dir" ``` ##### Set up shell completions (optional) Completions must be configured by adding the following to your `.bashrc`: ```shell . <(asdf completion bash) ``` ::: ::: details Fish ##### Add shims directory to path (required) Add the following to `~/.config/fish/config.fish`: ```shell # ASDF configuration code if test -z $ASDF_DATA_DIR set _asdf_shims "$HOME/.asdf/shims" else 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 PATH if not contains $_asdf_shims $PATH set -gx --prepend PATH $_asdf_shims end set --erase _asdf_shims ``` ###### Custom data directory (optional) **Pacman**: Completions are automatically configured on installation by the AUR package. Add the following to `~/.config/fish/config.fish` above the lines you added above: ```shell set -gx --prepend ASDF_DATA_DIR "/your/custom/data/dir" ``` ##### Set up shell completions (optional) Completions must be configured manually with the following command: ```shell $ asdf completion fish > ~/.config/fish/completions/asdf.fish ``` ::: ::: details Elvish ##### Add shims directory to path (required) Add the following to `~/.config/elvish/rc.elv`: ```shell var asdf_data_dir = ~'/.asdf' if (and (has-env ASDF_DATA_DIR) (!=s $E:ASDF_DATA_DIR '')) { set asdf_data_dir = $E:ASDF_DATA_DIR } if (not (has-value $paths $asdf_data_dir'/shims')) { set paths = [$path $@paths] } ``` ###### Custom data directory (optional) Change the following line in the above snippet to set a custom data directory: ```diff -var asdf_data_dir = ~'/.asdf' +var asdf_data_dir = '/your/custom/data/dir' ``` ##### Set up shell completions (optional) ```shell $ asdf completion elvish >> ~/.config/elvish/rc.elv $ echo "\n"'set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~' >> ~/.config/elvish/rc.elv ``` ::: ::: details ZSH **Pacman**: 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). ##### Add shims directory to path (required) Add the following to `~/.zshrc`: ```shell export PATH="${ASDF_DATA_DIR:-$HOME/.asdf}/shims:$PATH" ``` ###### Custom data directory (optional) Add the following to `~/.zshrc` above the line you added above: ```shell export ASDF_DATA_DIR="/your/custom/data/dir" ``` ##### Set up shell completions (optional) Completions are configured by either a ZSH Framework `asdf` plugin (like [asdf for oh-my-zsh](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/asdf)) or by doing the following: ```shell $ mkdir -p "${ASDF_DATA_DIR:-$HOME/.asdf}/completions" $ asdf completion zsh > "${ASDF_DATA_DIR:-$HOME/.asdf}/completions/_asdf" ``` Then add the following to your `.zshrc`: ```shell # append completions to fpath fpath=(${ASDF_DATA_DIR:-$HOME/.asdf}/completions $fpath) # initialise completions with ZSH's compinit autoload -Uz compinit && compinit ``` **Note** If you are using a custom `compinit` setup with a ZSH Framework, ensure `compinit` is below your sourcing of the framework Completions are configured by either a ZSH Framework `asdf` or will need to be [configured as per Homebrew's instructions](https://docs.brew.sh/Shell-Completion#configuring-completions-in-zsh). If you are using a ZSH Framework the associated plugin for asdf may need to be updated to use the new ZSH completions properly via `fpath`. The Oh-My-ZSH asdf plugin is yet to be updated, see [ohmyzsh/ohmyzsh#8837](https://github.com/ohmyzsh/ohmyzsh/pull/8837). ::: ::: details PowerShell Core ##### Add shims directory to path (required) Add the following to `~/.config/powershell/profile.ps1`: ```shell # Determine the location of the shims directory if ($null -eq $ASDF_DATA_DIR -or $ASDF_DATA_DIR -eq '') { $_asdf_shims = "${env:HOME}/.asdf/shims" } else { $_asdf_shims = "$ASDF_DATA_DIR/shims" } # Then add it to path $env:PATH = "${_asdf_shims}:${env:PATH}" ``` ###### Custom data directory (optional) Add the following to `~/.config/powershell/profile.ps1` above the snippet you added above: ```shell $env:ASDF_DATA_DIR = "/your/custom/data/dir" ``` Shell completions not available for PowerShell ::: ::: details Nushell ##### Add shims directory to path (required) Add the following to `~/.config/nushell/config.nu`: ```shell let shims_dir = ( if ( $env | get --ignore-errors ASDF_DATA_DIR | is-empty ) { $env.HOME | path join '.asdf' } else { $env.ASDF_DATA_DIR } | path join 'shims' ) $env.PATH = ( $env.PATH | split row (char esep) | where { |p| $p != $shims_dir } | prepend $shims_dir ) ``` ###### Custom data directory (optional) Add the following to `~/.config/nushell/config.nu` above the line you added above: ```shell $env.ASDF_DATA_DIR = "/your/custom/data/dir" ``` ##### Set up shell completions (optional) ```shell # If you've not customized the asdf data directory: $ mkdir $"($env.HOME)/.asdf/completions" $ asdf completion nushell | save $"($env.HOME)/.asdf/completions/nushell.nu" # If you have customized the data directory by setting ASDF_DATA_DIR: $ mkdir $"($env.ASDF_DATA_DIR)/completions" $ asdf completion nushell | save $"($env.ASDF_DATA_DIR)/completions/nushell.nu" ``` Then add the following to `~/.config/nushell/config.nu`: ```shell let asdf_data_dir = ( if ( $env | get --ignore-errors ASDF_DATA_DIR | is-empty ) { $env.HOME | path join '.asdf' } else { $env.ASDF_DATA_DIR } ) . "$asdf_data_dir/completions/nushell.nu" ``` ::: ::: details POSIX Shell ##### Add shims directory to path (required) Add the following to `~/.profile`: ```shell export PATH="${ASDF_DATA_DIR:-$HOME/.asdf}/shims:$PATH" ``` ###### Custom data directory (optional) Add the following to `~/.profile` above the line you added above: ```shell export ASDF_DATA_DIR="/your/custom/data/dir" ``` ::: `asdf` scripts need to be sourced **after** you have set your `$PATH` and **after** you have sourced your framework (oh-my-zsh etc). Restart your shell so that `PATH` changes take effect. Opening a new terminal tab will usually do it. ## Core Installation Complete! This completes the installation of the `asdf` core :tada: `asdf` is only useful once you install a **plugin**, install a **tool** and manage its **versions**. Continue the guide below to learn how to do this. ## 4. Install a Plugin For demonstration purposes we will install & set [Node.js](https://nodejs.org/) via the [`asdf-nodejs`](https://github.com/asdf-vm/asdf-nodejs/) plugin. ### Plugin Dependencies Each plugin has dependencies so we need to check the plugin repo where they should be listed. For `asdf-nodejs` they are: | OS | Dependency Installation | | ------------------------------ | --------------------------------------- | | Debian | `apt-get install dirmngr gpg curl gawk` | | CentOS/ Rocky Linux/ AlmaLinux | `yum install gnupg2 curl gawk` | | macOS | `brew install gpg gawk` | We should install dependencies first as some Plugins have post-install hooks. ### Install the Plugin ```shell asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git ``` ## 5. Install a Version Now we have a plugin for Node.js we can install a version of the tool. We can see which versions are available with `asdf list all nodejs` or a subset of versions with `asdf list all nodejs 14`. We will just install the `latest` available version: ```shell asdf install nodejs latest ``` ::: tip Note `asdf` enforces exact versions. `latest` is a helper throughout `asdf` that will resolve to the actual version number at the time of execution. ::: ## 6. Set a Version `asdf` performs a version lookup of a tool in all `.tool-versions` files from the current working directory up to the `$HOME` directory. The lookup occurs just-in-time when you execute a tool that `asdf` manages. ::: warning Without a version listed for a tool execution of the tool will **error**. `asdf current` will show you the tool & version resolution, or absence of, from your current directory so you can observe which tools will fail to execute. ::: Because asdf looks for a `.tool-versions` file in the current directory first, and if the file is not found it then climbs up the file tree looking for a `.tool-versions` in a parent directory until it finds one. If no `.tool-versions` file is found the version resolution process will fail and an error will be printed. If you want to set a default version that will apply to all directories you work in you can set a version in `$HOME/.tool-versions`. Any directory under your home directory will have that same version set, unless a particular directory sets another version. ```shell asdf set -u nodejs 16.5.0 ``` `$HOME/.tool-versions` will then look like: ``` nodejs 16.5.0 ``` Some OSs already have tools installed that are managed by the system and not `asdf`, `python` is a common example. You need to tell `asdf` to pass the management back to the system. The [Versions reference section](/manage/versions.md) will guide you. The first place asdf looks for a version is your current working directory (`$PWD/.tool-versions`). This may be a directory containing a source code or Git repository for a project. When in your desired directory execute you can use `asdf set` to set the version: ```shell asdf set nodejs 16.5.0 ``` `$PWD/.tool-versions` will then look like: ``` nodejs 16.5.0 ``` ### Using Existing Tool Version Files `asdf` supports the migration from existing version files from other version managers. Eg: `.ruby-version` for the case of `rbenv`. This is supported on a per-plugin basis. [`asdf-nodejs`](https://github.com/asdf-vm/asdf-nodejs/) supports this via both `.nvmrc` and `.node-version` files. To enable this, add the following to your `asdf` configuration file `$HOME/.asdfrc`: ``` legacy_version_file = yes ``` See the [configuration](/manage/configuration.md) reference page for more config options. ## Guide Complete! That completes the Getting Started guide for `asdf` :tada: You can now manage `nodejs` versions for your project. Follow similar steps for each type of tool in your project! `asdf` has many more commands to become familiar with, you can see them all by running `asdf --help` or `asdf`. The core of the commands are broken into three categories: - [core `asdf`](/manage/core.md) - [plugins](/manage/plugins.md) - [versions (of tools)](/manage/versions.md) ================================================ FILE: docs/guide/introduction.md ================================================ # Introduction `asdf` is a tool version manager. All tool version definitions are contained within one file (`.tool-versions`) which you can check in to your project's Git repository to share with your team, ensuring everyone is using the **exact** same versions of tools. The old way of working required multiple CLI version managers, each with their distinct API, configurations files and implementation (e.g. `$PATH` manipulation, shims, environment variables, etc...). `asdf` provides a single interface and configuration file to simplify development workflows, and can be extended to all tools and runtimes via a simple plugin interface. ## How It Works Once `asdf` core is set up with your Shell configuration, plugins are installed to manage particular tools. When a tool is installed by a plugin, the executables that are installed have [shims]() created for each of them. When you try and run one of these executables, the shim is run instead, allowing `asdf` to identify which version of the tool is set in `.tool-versions` and execute that version. ## Related Projects ### nvm / n / rbenv etc Tools like [nvm](https://github.com/nvm-sh/nvm), [n](https://github.com/tj/n) and [rbenv](https://github.com/rbenv/rbenv) are all written as Shell scripts which create shims for the executables installed by these tools. `asdf` is very similar and was built to compete in this space of tool/runtime version management. The differentiating factor for `asdf` is its plugin system which removes the need for a manager per tool/runtime, different commands per manager and different `*-version` files in your repo. ### direnv > augments existing shells with a new feature that can load and unload environment variables depending on the current directory. `asdf` does not manage Environment Variables, however there is a plugin [`asdf-direnv`](https://github.com/asdf-community/asdf-direnv) to integrate direnv behaviour with `asdf`. See [direnv docs](https://direnv.net/) for more. ### Homebrew > The Missing Package Manager for macOS (or Linux) Homebrew manages your packages and their upstream dependencies. `asdf` does not manage upstream dependencies, it is not a package manager, that burden is upon the user, though we try and keep the dependency list small. See [Homebrew docs](https://brew.sh/) for more. ### NixOS > Nix is a tool that takes a unique approach to package management and system configuration NixOS aims to build truly reproducible environments by managing exact versions of packages up the entire dependency tree of each tool, something `asdf` does not do. NixOS does this with its own programming language, many CLI tools and a package collection of over 60,000 packages. Again, `asdf` does not manage upstream dependencies and is not a package manager. See [NixOS docs](https://nixos.org/guides/how-nix-works.html) for more. ## Why use asdf? `asdf` ensures teams are using the **exact** same versions of tools, with support for **many** tools via a plugin system, and the _simplicity and familiarity_ of being a single **Shell** script you include in your Shell config. ::: tip Note `asdf` is not intended to be a system package manager. It is a tool version manager. Just because you can create a plugin for any tool and manage its versions with `asdf`, does not mean that is the best course of action for that specific tool. ::: ================================================ FILE: docs/guide/upgrading-to-v0-16.md ================================================ # Upgrading to 0.16.0 asdf versions 0.15.0 and older were written in Bash and distributed as a set of Bash scripts with the `asdf` function loaded into your shell. asdf version 0.16.0 is a complete rewrite of asdf in Go. Since it is a complete rewrite there are a [number of breaking](#breaking-changes) changes and it is now a binary rather than a set of scripts. ## Installation Installation of version 0.16.0 and newer is much simpler than previous versions of asdf. It's just three steps: * Download the appropriate `asdf` binary for your operating system/architecture combo via [any of the install methods available](/guide/getting-started.html#_1-install-asdf). If using a package manager verify it's installing version 0.16.0 or later. * Add `$ASDF_DATA_DIR/shims` to the front of your `$PATH`. * Optionally, if you previously had a customized location for asdf data, set `ASDF_DATA_DIR` to the directory you already had the old version installing plugins, versions, and shims. If your operating system's package manager already offers asdf 0.16.0 that is probably the best method for installing it. Upgrading asdf is now only possible via OS package managers and manual installation. There is no self-upgrade feature. ### Upgrading Without Losing Data You can upgrade to the latest version of asdf without losing your existing install data. It's the same sequence of steps as above. #### 1. Download the appropriate `asdf` binary for your operating system & architecture Download the binary from the [GitHub releases page](https://github.com/asdf-vm/asdf/releases) and place it in a directory on your path. I chose to place the asdf binary in `$HOME/bin` and then added `$HOME/bin` to the front of my `$PATH`: ``` # In .zshrc, .bashrc, etc... export PATH="$HOME/bin:$PATH" ``` #### 2. Set `ASDF_DATA_DIR` Run `asdf info` and copy the line containing the `ASDF_DATA_DIR` variable: ``` ... ASDF_DATA_DIR="/home/myuser/.asdf" ... ``` In your shell RC file (`.zshrc` if Zsh, `.bashrc` if Bash, etc...) add a line to the end setting `ASDF_DATA_DIR` to that same value: ```bash export ASDF_DATA_DIR="/home/myuser/.asdf" ``` #### 3. Add `$ASDF_DATA_DIR/shims` to the front of your `$PATH` In your shell RC file (same file as step #2) add `$ASDF_DATA_DIR/shims` to the front of your path: ```bash export ASDF_DATA_DIR="/home/myuser/.asdf" export PATH="$ASDF_DATA_DIR/shims:$PATH" ``` #### 4. Remove Old Config In your shells RC file you'll have the old code running the asdf shell script at startup. It'll probably look something like this: ``` . "$HOME/.asdf/asdf.sh" ``` Or this: ``` . /opt/homebrew/opt/asdf/libexec/asdf.sh ``` Comment out these lines or remove them entirely. If you are not using Zsh or Bash please see the legacy [Getting Started guide](https://asdf-vm.com/guide/getting-started-legacy.html#_3-install-asdf) for the code snippet you need to remove. #### 5. Regenerate Shims Verify that `asdf` command in your shell session is version 0.16.0+ by running `asdf --help`. If you still see an older version you will need to start a new shell session. Once you've verified the `asdf` command is the new version run `asdf reshim` to regenerate all shims. This is necessary as the old shims may still reference the old Bash version. ### Testing If you aren't sure if the upgrade to 0.16.0 will break things for you can you can test by installing 0.16.0 in addition to your existing version as described above in "Upgrading Without Losing Data". If it turns out that the upgrade to 0.16.0 or greater breaks things for you you can revert back to the older version. Remove the lines you added to your shell RC file, and add back in the lines you removed or commented out. ### Removing old files **Only do this after you've completed all the steps above and have verified your new asdf installation is working correctly!** After upgrade there are various files you can remove from the old Bash-script based versions of asdf. Most of the files in your data directory (typically `~/.asdf/`) can be removed. Note that you don't have to do this. There is no harm in keeping the files from old versions of asdf around. The only directories that must be **kept** are: * `downloads/` * `installs/` * `plugins/` * `shims/` The rest can be deleted. This can be done in one command with `find`: ``` find ${ASDF_DATA_DIR:-$HOME/.asdf}/ -maxdepth 1 -mindepth 1 -not -name downloads -not -name plugins -not -name installs -not -name shims -exec rm -r {} \; ``` ## Breaking Changes ### Hyphenated commands have been removed asdf version 0.15.0 and earlier supported by hyphenated and non-hyphenated versions of certain commands. With version 0.16.0 only the non-hyphenated versions are supported. The affected commands: * `asdf list-all` -> `asdf list all` * `asdf plugin-add` -> `asdf plugin add` * `asdf plugin-list` -> `asdf plugin list` * `asdf plugin-list-all` -> `asdf plugin list all` * `asdf plugin-update` -> `asdf plugin update` * `asdf plugin-remove` -> `asdf plugin remove` * `asdf plugin-test` -> `asdf plugin test` * `asdf shim-versions` -> `asdf shimversions` ### `asdf global` and `asdf local` commands have been replaced with `asdf set` `asdf global` and `asdf local` have been removed. The "global" and "local" terminology was wrong and also misleading. asdf doesn't actually support "global" versions that apply everywhere. Any version that was specified with `asdf global` could easily be overridden by a `.tool-versions` file in your current directory specifying a different version. This was confusing to users. The new `asdf set` behaves the same as `asdf local` by default, but also has flags for setting versions in the user's home directory (`--home`) and in an existing `.tool-versions` file in one of the parent directories (`--parent`). This new interface will hopefully convey a better understanding of how asdf resolves versions and provide equivalent functionality. ### `asdf update` command has been removed Updates can no longer be performed this way. Use your OS package manager or download the latest binary manually. Additionally, the `asdf update` command present in versions 0.15.0 and older cannot upgrade to version 0.16.0 because the install process has changed. **You cannot upgrade to the latest Go implementation using `asdf update`.** ### `asdf shell` command has been removed This command actually set an environment variable in the user's current shell session. It was able to do this because `asdf` was actually a shell function, not an executable. The new rewrite removes all shell code from asdf, and it is now a binary rather than a shell function, so setting environment variables directly in the shell is no longer possible. ### `asdf current` has changed Instead of three columns in the output, with the last being either the location the version is set or a suggested command that could be run to set or install a version. The third column has been split into two columns. The third column now only indicates the source of the version if it is set (typically either version file or environment variable) and the fourth is a boolean indicating whether the specified version is actually installed. If it is not installed, a suggested install command is shown. ### Plugin extension commands must now be prefixed with `cmd` Previously plugin extension commands could be run like this: ``` asdf nodejs nodebuild --version ``` Now they must be prefixed with `cmd` to avoid causing confusion with built-in commands: ``` asdf cmd nodejs nodebuild --version ``` ### Extension commands have been redesigned There are a number of breaking changes for plugin extension commands: * They must be runnable by `exec` syscall. If your extension commands are shell scripts in order to be run with `exec` they must start with a proper shebang line. * They can now be binaries or scripts in any language. It no longer makes sense to require a `.bash` extension as it is misleading. * They must have executable permission set. * They are no longer sourced by asdf as Bash scripts when they lack executable permission. Additionally, only the first argument after plugin name is used to determine the extension command to run. This means effectively there is the default `command` extension command that asdf defaults to when no command matching the first argument after plugin name is found. For example: ``` foo/ lib/commands/ command command-bar command-bat-man ``` Previously these scripts would work like this: ``` $ asdf cmd foo # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command` $ asdf cmd foo bar # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-bar` $ asdf cmd foo bat man # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-bat-man` ``` Now: ``` $ asdf cmd foo # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command` $ asdf cmd foo bar # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-bar` $ asdf cmd foo bat man # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-bat man` ``` ### Executables Shims Resolve to Must Runnable by `syscall.Exec` The most obvious example of this breaking change are scripts that lack a proper shebang line. asdf 0.15.0 and older were implemented in Bash, so as long it was an executable that could be executed with Bash it would run. This mean that scripts lacking a shebang could still be run by `asdf exec`. With asdf 0.16.x implemented in Go we now invoke executables via Go's `syscall.Exec` function, which cannot handle scripts lacking a shebang. In practice this isn't much of a problem. Most shell scripts DO contain a shebang line. If a tool managed by asdf provides scripts that don't have a shebang line one will need to be added to them. ### Custom shim templates are no longer supported This was a rarely used feature. The only plugin maintained by the core team that used it was the Elixir plugin, and it no longer needs it. This feature was originally added so that shim that get evaluated by a program rather than executed contain code that is suitable for evaluation by a particular program (in the case of Elixir this was the `iex` shell). Upon further investigation it seems this feature only exists because the `PATH` for executables was sometimes improperly set to include the **shims** rather than the other **executables** for the selected version(s). ================================================ FILE: docs/index.md ================================================ --- # https://vitepress.dev/reference/default-theme-home-page layout: home hero: name: asdf text: The Multiple Runtime Version Manager tagline: Manage all your runtime versions with one tool! actions: - theme: brand text: Get Started link: /guide/getting-started - theme: alt text: What is asdf? link: /guide/introduction - theme: alt text: View on GitHub link: https://github.com/asdf-vm/asdf features: - title: One Tool details: "Manage each of your project runtimes with a single CLI tool and command interface." icon: 🎉 - title: Plugins details: "Large ecosystem of existing runtimes & tools. Simple API to add support for new tools as you need!" icon: 🔌 - title: Backwards Compatible details: "Support for existing config files .nvmrc, .node-version, .ruby-version for smooth migration!" icon: ⏮ - title: "One Config File" details: ".tool-versions to manage all your tools, runtimes and their versions in a single, sharable place." icon: 📄 - title: "Shells" details: "Supports Bash, ZSH, Fish & Elvish with completions available." icon: 🐚 - title: "GitHub Actions" details: "Provides a GitHub Action to install and utilize your .tool-versions in your CI/CD workflows." icon: 🤖 --- ================================================ FILE: docs/ja-jp/contribute/core.md ================================================ # asdf これは、`asdf`コアのコントリビューションガイドです。 ## 初期セットアップ GitHubで`asdf`をフォークするか、デフォルトのブランチをGitクローンしてください: ```shell # clone your fork git clone https://github.com//asdf.git # or clone asdf git clone https://github.com/asdf-vm/asdf.git ``` コア開発用のツールは、このリポジトリの`.tool-versions`で定義されています。`asdf`自身でこれらのツールを管理したい場合は、下記のようにプラグインを追加してください: ```shell asdf plugin add bats https://github.com/timgluz/asdf-bats.git asdf plugin add shellcheck https://github.com/luizm/asdf-shellcheck.git asdf plugin add shfmt https://github.com/luizm/asdf-shfmt.git ``` `asdf`の開発に必要なバージョンを、下記のようにインストールします: ```shell asdf install ``` 開発ツールに影響を与える特定の機能を壊す可能性もあるため、ローカルマシンで開発する際は、`asdf`を使用しないほうが _良いかもしれません_ 。下記に、使用しているツールを列挙します: - [bats-core](https://github.com/bats-core/bats-core): BashまたはPOSIX準拠のスクリプトを単体テストするための、Bash自動テストシステムです。 - [shellcheck](https://github.com/koalaman/shellcheck): シェルスクリプトの静的解析ツールです。 - [shfmt](https://github.com/mvdan/sh): Bashをサポートするシェルパーサ、フォーマッタ、インタプリタです。 ## 開発 インストール済みの`asdf`に変更を加えずに、あなたが開発した変更内容を試したいときは、`$ASDF_DIR`変数に、クローンしたリポジトリのパスを設定し、そのディレクトリの`bin`と`shims`ディレクトリを一時的にパスの先頭へ追加します。 リモートにコミットまたはプッシュする前に、コードをローカルでフォーマット、Lint、およびテストすることを推奨します。その際は、次のスクリプト/コマンドを使用してください: ```shell # Lint ./scripts/lint.bash --check # Fix & Format ./scripts/lint.bash --fix # Test: all tests ./scripts/test.bash # Test: for specific command bats test/list_commands.bash ``` ::: tip ヒント **テストを作ってください!** - 新機能にとってテストは**必要不可欠**であり、バグ修正のレビューをスピードアップさせることができます。プルリクエストを作成する前に、新しいコードをカバーするようなテストを作成してください。[bats-coreのドキュメント](https://bats-core.readthedocs.io/en/stable/index.html)もご覧ください。 ::: ### Gitignore 下記は、`asdf-vm/asdf`リポジトリの`.gitignore`ファイルです。プロジェクト固有のファイルは無視をしています。使用しているOS、ツール、およびワークフロー固有のファイルは、グローバルな`.gitignore`構成で無視する必要があります。詳しくは[こちら](http://stratus3d.com/blog/2018/06/03/stop-excluding-editor-temp-files-in-gitignore/)をご覧ください。 @[Gitignoreコード](https://github.com/asdf-vm/asdf/blob/master/.gitignore) ### `.git-blame-ignore-revs` `asdf`では、`.git-blame-ignore-revs`を使用して、Blameを実行する際のノイズを減らしています。詳しくは、[git blameのドキュメント](https://git-scm.com/docs/git-blame)をご覧ください。 `git blame`を実行するときは、下記のように、このファイルと共に使います: ```sh git blame --ignore-revs-file .git-blame-ignore-revs ./test/install_command.bats ``` 毎回手動でファイルを指定しなくても、gitのオプションで、`blame`を呼び出すたびにこのファイルを使うように設定することもできます: ```sh git config blame.ignoreRevsFile .git-blame-ignore-revs ``` このファイルを使用するように、IDEを設定することもできます。例えば、VSCode(および[GitLens](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens))を使う場合は、`.vscode/settings.json`に下記のように記述します: ```json { "gitlens.advanced.blame.customArguments": [ "--ignore-revs-file", ".git-blame-ignore-revs" ] } ``` ## Batsテスト ローカルでテストを実行するには、下記のようにテストを呼び出します: ```shell ./scripts/test.bash ``` テストを作成する前に、**下記項目を一通り参照してください**: - `test/`内にすでに作成されているテスト - [bats-coreのドキュメント](https://bats-core.readthedocs.io/en/stable/index.html) - `scripts/test.bash`で使用されている既存のBatsの設定 ### Batsのヒント Batsでのデバッグは、難しいことがあります。`-t`フラグを指定してTAP出力を有効にすると、テスト実行中に特殊なファイルディスクリプタ`>&3`を使用して出力を表示できるため、デバッグが簡単になります。例えば次のとおりです: ```shell # test/some_tests.bats printf "%s\n" "Will not be printed during bats test/some_tests.bats" printf "%s\n" "Will be printed during bats -t test/some_tests.bats" >&3 ``` 詳しくは、bats-coreドキュメント内の[Printing to the Terminal](https://bats-core.readthedocs.io/en/stable/writing-tests.html#printing-to-the-terminal)で説明されています。 ## プルリクエスト、リリース、Conventional Commits `asdf`は、[Release Please](https://github.com/googleapis/release-please)という自動リリースツールを使用して、[セマンティックバージョン](https://semver.org/)を自動的に引き上げ、[Changelog](https://github.com/asdf-vm/asdf/blob/master/CHANGELOG.md)を生成しています。この情報は、前回のリリースからのコミット履歴を読み込むことで生成されます。 [Conventional Commit messages](https://www.conventionalcommits.org/ja/)では、デフォルトブランチでのコミットメッセージのフォーマットとなる、プルリクエストタイトルのフォーマットを定義しています。これは、GitHub Action[`amannn/action-semantic-pull-request`](https://github.com/amannn/action-semantic-pull-request)で強制されます。 Conventional Commitは、下記のフォーマットに従います: ``` [optional scope][optional !]: fix: some fix feat: a new feature docs: some documentation update docs(website): some change for the website feat!: feature with breaking change ``` ``の種類は次のとおりです: `feat`、`fix`、`docs`、`style`、 `refactor`、 `perf`、`test`、`build`、`ci`、`chore`、 `revert`。 - `!`: 破壊的変更を示します - `fix`: セマンティックバージョンの`patch`を新しく作成します - `feat`: セマンティックバージョンの`minor`を新しく作成します - `!`: セマンティックバージョンの`major`を新しく作成します プルリクエストのタイトルは、このフォーマットに従う必要があります。 ::: tip ヒント プルリクエストのタイトルには、Conventional Commit messageのフォーマットを使用してください。 ::: ## Dockerイメージ [asdf-alpine](https://github.com/vic/asdf-alpine)および[asdf-ubuntu](https://github.com/vic/asdf-ubuntu)プロジェクトは、一部のasdfツールのDocker化されたイメージを提供する取り組みを継続的に行っています。これらのDockerイメージは、開発用サーバのベースとしたり、本番用アプリケーションの実行用途として使用することができます。 ================================================ FILE: docs/ja-jp/contribute/documentation.md ================================================ # ドキュメント & サイト これは、ドキュメントおよびサイトのコントリビューションガイドです。 ## 初期セットアップ GitHubで`asdf`をフォークするか、デフォルトのブランチをGitクローンしてください: ```shell # clone your fork git clone https://github.com//asdf.git # or clone asdf git clone https://github.com/asdf-vm/asdf.git ``` ドキュメントサイト開発用のツールは、`asdf`によって`docs/.tool-versions`で管理されています。下記のようにプラグインを追加してください: ```shell asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs ``` 開発に必要なバージョンを、下記のようにインストールします: ```shell asdf install ``` - [Node.js](https://nodejs.org): ChromeのV8 JavaScriptエンジンをベースに構築されたJavaScriptランタイムです。 `docs/package.json`をもとに、Node.jsの依存関係をインストールしてください: ```shell npm install ``` ## 開発 [VitePress (v2)](https://vitepress.dev/)は、asdfドキュメントサイトを構築するために使用している静的サイトジェネレータ(SSG)です。類似ツールである[Docsify.js](https://docsify.js.org/)やVuePressに代わってVitePressが採用されたのは、ユーザがJavaScriptを使用できない、または有効にしていない場合に、HTMLのみのフォールバックをサポートしたいからでした。これは、DocsifyとVitePressがVuePressに急速に取って代わっていた場合には不可能でした。これ以外の機能セットはほとんど同じで、最小限の構成でMarkdownファイルを書くことに重点を置いています。 `package.json`には、開発に必要なスクリプトが含まれています: @[`package.json`のコード](https://github.com/asdf-vm/asdf/blob/master/docs/package.json#L3-L5) ローカルの開発サーバを起動するには、次のように実行します: ```shell npm run dev ``` コミットする前にコードをフォーマットするには、次のように実行します: ```shell npm run format ``` ## プルリクエスト、リリース、Conventional Commits `asdf`は、プルリクエストタイトルのConventional Commitsに依存する自動リリースパイプラインを使用しています。詳しくは、[コアのコントリビューションガイド](./core.md)のドキュメントに記述されています。 ドキュメントの変更に関するプルリクエストを作成する場合、プルリクエストのタイトルは、Conventional Commit typeを`docs`として、`docs: `というフォーマットで作成するようにしてください。 ## Vitepress サイトの構成設定は、構成を示すために使用されるJSオブジェクト含んだ、いくつかのTypeScriptファイルに記述されています。以下のとおりです: - `docs/.vitepress/config.js`: サイトのルート構成ファイルです。仕様については、[VitePressのドキュメント](https://vitepress.dev/reference/site-config)をご覧ください。 ルート構成ファイルを簡素化するために、 _Navバー_ と _サイドバー_ の構成を示す大きなJSオブジェクトについては、別ファイルに切り出されており、かつ、ロケールごとに分類されています。次の両方のファイルを参照してください: - `docs/.vitepress/navbars.js` - `docs/.vitepress/sidebars.js` これらの構成設定に関する公式ドキュメントは、[Default Theme Reference](https://vitepress.dev/reference/default-theme-config)をご覧ください。 ## I18n VitePressは、国際化対応に関して最高のサポートを備えています。 ルート構成ファイルである`docs/.vitepress/config.js`では、サポートされているロケールとそのURL、ドロップメニューのタイトル、Navバー/サイドバーの構成への参照を定義しています。 Navバー/サイドバーの構成設定は前述の構成ファイルにキャプチャされ、ロケールごとに分類され、個別にエクスポートされます。 各ロケールのMarkdownコンテンツは、ルート構成ファイル内の`locales`内のキーと同じ名前のディレクトリ配下に配置する必要があります。ルート構成が下記の場合: ```js // docs/.vitepress/config.js export default defineConfig({ ... locales: { root: { label: "English", lang: "en-US", themeConfig: { nav: navbars.en, sidebar: sidebars.en, }, }, "pt-br": { label: "Brazilian Portuguese", lang: "pr-br", themeConfig: { nav: navbars.pt_br, sidebar: sidebars.pt_br, }, }, "zh-hans": { label: "简体中文", lang: "zh-hans", themeConfig: { nav: navbars.zh_hans, sidebar: sidebars.zh_hans, }, }, }, }) ``` `/pt-BR/`を有効にするには、下記のように、`docs/pt-BR/`配下に同じMarkdownファイルのセットを配置する必要があります: ```shell docs ├─ README.md ├─ foo.md ├─ nested │ └─ README.md └─ pt-BR ├─ README.md ├─ foo.md └─ nested └─ README.md ``` [公式のVitePress i18nドキュメント](https://vitepress.dev/guide/i18n)には、より詳細な説明が記述されています。 ================================================ FILE: docs/ja-jp/contribute/first-party-plugins.md ================================================ # 公式プラグイン asdfコアチームでは、日々のワークライフに関連するプラグインをいくつか作成しています。これらのプラグインのメンテナンスおよび改善にご協力いただける方を、いつでも歓迎しています。詳しくは、下記リンクから、それぞれのリポジトリを参照してください: - [Elixir](https://github.com/asdf-vm/asdf-elixir) - [Erlang](https://github.com/asdf-vm/asdf-erlang) - [Node.js](https://github.com/asdf-vm/asdf-nodejs) - [Ruby](https://github.com/asdf-vm/asdf-ruby) コミュニティプラグインについては、下記をご覧ください: - [`asdf-community`オーガナイゼーション](https://github.com/asdf-community): `asdf`プラグインの長期的なメンテナンスを目的としたコミュニティ主導の共同プロジェクトです。 - [`asdf-plugins`ショートネームリポジトリ](https://github.com/asdf-vm/asdf-plugins): ポピュラーな`asdf`プラグインを検索するために`asdf`コアが使用する、ショートネームのリストです。 - [GitHub `asdf-plugin`トピック検索](https://github.com/topics/asdf-plugin) ================================================ FILE: docs/ja-jp/contribute/github-actions.md ================================================ # GitHub Actions ご興味を持っていただきありがとうございます。既存のイシュー、議論、コントリビューションガイドについては、[asdf actions リポジトリ](https://github.com/asdf-vm/actions)をご覧ください。 ================================================ FILE: docs/ja-jp/guide/getting-started-legacy.md ================================================ # はじめよう `asdf`のインストールには次の手順が必要です: 1. 依存関係のインストール 2. `asdf`コアのダウンロード 3. `asdf`のインストール 4. 管理したいツール/ランタイムごとにプラグインをインストール 5. ツール/ランタイムの特定バージョンをインストール 6. `.tool-versions`ファイルで、グローバルまたはプロジェクトのバージョンをセット ## 1. 依存関係のインストール asdfの動作には`git`および`curl`が必要です。以下の表は、 _あなたが使用している_ パッケージマネージャで実行するコマンドの _一部例_ です(いくつかのツールは、後の手順で自動的にインストールされます)。 | OS | パッケージマネージャ | コマンド | | ----- | -------------------- | ---------------------------------- | | linux | Aptitude | `apt install curl git` | | linux | DNF | `dnf install curl git` | | linux | Pacman | `pacman -S curl git` | | linux | Zypper | `zypper install curl git` | | macOS | Homebrew | `brew install coreutils curl git` | | macOS | Spack | `spack install coreutils curl git` | ::: tip 備考 お使いのシステムの構成によっては、接頭に`sudo`が必要となる場合もあります。 ::: ## 2. asdfのダウンロード ### 公式ダウンロード ```shell git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.14.0 ``` ### コミュニティがサポートするダウンロード方法 理由がない限り、`git`コマンドを使用した公式ダウンロードの手順を使用することを強く推奨します。 | 方法 | コマンド | | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Homebrew | `brew install asdf` | | Pacman | `git clone https://aur.archlinux.org/asdf-vm.git && cd asdf-vm && makepkg -si` または好みの[AURヘルパー](https://wiki.archlinux.jp/index.php/AUR_ヘルパー)を使用 | ## 3. asdfのインストール あなたが使用しているシェル、OS、およびインストール方法によって、ここでの設定方法が変わります。最も適したものを選択してください。 **masOSユーザの方は、この節の最後にある`path_helper`に関する警告を必ず参照してください。** ::: details Bash & Git `~/.bashrc`に下記の行を追記します: ```shell . "$HOME/.asdf/asdf.sh" ``` コマンド補完が必要な場合は、`.bashrc`に下記の行を追記します: ```shell . "$HOME/.asdf/completions/asdf.bash" ``` ::: ::: details Bash & Git (macOS) **macOS Catalina以降**を使用している場合、デフォルトのシェルは**ZSH**です。Bashに変更していない限り、ZSHの手順を参照してください。 `~/.bash_profile`に下記の行を追記します: ```shell . "$HOME/.asdf/asdf.sh" ``` コマンド補完が必要な場合は、`.bash_profile`に下記の行を追記します: ```shell . "$HOME/.asdf/completions/asdf.bash" ``` ::: ::: details Bash & Homebrew 下記コマンドで、`~/.bashrc`に`asdf.sh`を追加します: ```shell echo -e "\n. \"$(brew --prefix asdf)/libexec/asdf.sh\"" >> ~/.bashrc ``` コマンド補完が必要な場合は、[Homebrewのガイドに従って設定を完了させる](https://docs.brew.sh/Shell-Completion#configuring-completions-in-bash)か、下記コマンドを実行します: ```shell echo -e "\n. \"$(brew --prefix asdf)/etc/bash_completion.d/asdf.bash\"" >> ~/.bashrc ``` ::: ::: details Bash & Homebrew (macOS) **macOS Catalina以降**を使用している場合、デフォルトのシェルは**ZSH**です。Bashに変更していない限り、ZSHの手順を参照してください。 下記コマンドで、`~/.bash_profile`に`asdf.sh`を追加します: ```shell echo -e "\n. \"$(brew --prefix asdf)/libexec/asdf.sh\"" >> ~/.bash_profile ``` コマンド補完が必要な場合は、[Homebrewのガイドに従って設定を完了させる](https://docs.brew.sh/Shell-Completion#configuring-completions-in-bash)か、下記コマンドを実行します: ```shell echo -e "\n. \"$(brew --prefix asdf)/etc/bash_completion.d/asdf.bash\"" >> ~/.bash_profile ``` ::: ::: details Bash & Pacman `~/.bashrc`に下記の行を追記します: ```shell . /opt/asdf-vm/asdf.sh ``` コマンド補完が必要な場合は、[`bash-completion`](https://wiki.archlinux.jp/index.php/Bash#プログラムとオプションを追加)をインストールします。 ::: ::: details Fish & Git `~/.config/fish/config.fish`に下記の行を追記します: ```shell source ~/.asdf/asdf.fish ``` コマンド補完が必要な場合は、下記コマンドを実行します: ```shell mkdir -p ~/.config/fish/completions; and ln -s ~/.asdf/completions/asdf.fish ~/.config/fish/completions ``` ::: ::: details Fish & Homebrew 下記コマンドで、`~/.config/fish/config.fish`に`asdf.sh`を追加します: ```shell echo -e "\nsource "(brew --prefix asdf)"/libexec/asdf.fish" >> ~/.config/fish/config.fish ``` コマンド補完は、[Fish shellのHomebrewが担います](https://docs.brew.sh/Shell-Completion#configuring-completions-in-fish)。親切ですね! ::: ::: details Fish & Pacman `~/.config/fish/config.fish`に下記の行を追記します: ```shell source /opt/asdf-vm/asdf.fish ``` コマンド補完は、AURパッケージのインストール時に自動的に設定されます。 ::: ::: details Elvish & Git 下記コマンドで、`~/.config/elvish/rc.elv`に`asdf.elv`を追加します: ```shell mkdir -p ~/.config/elvish/lib; ln -s ~/.asdf/asdf.elv ~/.config/elvish/lib/asdf.elv echo "\n"'use asdf _asdf; var asdf~ = $_asdf:asdf~' >> ~/.config/elvish/rc.elv echo "\n"'set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~' >> ~/.config/elvish/rc.elv ``` コマンド補完は自動的に設定されます。 ::: ::: details Elvish & Homebrew 下記コマンドで、`~/.config/elvish/rc.elv`に`asdf.elv`を追加します: ```shell mkdir -p ~/.config/elvish/lib; ln -s (brew --prefix asdf)/libexec/asdf.elv ~/.config/elvish/lib/asdf.elv echo "\n"'use asdf _asdf; var asdf~ = $_asdf:asdf~' >> ~/.config/elvish/rc.elv echo "\n"'set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~' >> ~/.config/elvish/rc.elv ``` コマンド補完は自動的に設定されます。 ::: ::: details Elvish & Pacman 下記コマンドで、`~/.config/elvish/rc.elv`に`asdf.elv`を追加します: ```shell mkdir -p ~/.config/elvish/lib; ln -s /opt/asdf-vm/asdf.elv ~/.config/elvish/lib/asdf.elv echo "\n"'use asdf _asdf; var asdf~ = $_asdf:asdf~' >> ~/.config/elvish/rc.elv echo "\n"'set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~' >> ~/.config/elvish/rc.elv ``` コマンド補完は自動的に設定されます。 ::: ::: details ZSH & Git `~/.zshrc`に下記の行を追記します: ```shell . "$HOME/.asdf/asdf.sh" ``` **または**、[asdf for oh-my-zsh](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/asdf)のようなZSHフレームワークプラグインを使用して、このスクリプトをsourceし、コマンド補完をセットアップします。 コマンド補完は、ZSHフレームワークの`asdf`プラグインで設定するか、`~/.zshrc`に下記の行を追記することで設定できます: ```shell # append completions to fpath fpath=(${ASDF_DIR}/completions $fpath) # initialise completions with ZSH's compinit autoload -Uz compinit && compinit ``` - `compinit`のセットアップをカスタマイズしている場合は、`asdf.sh`ソース以下に`compinit`がくるようにしてください。 - ZSHフレームワークで`compinit`のセットアップをカスタマイズしている場合は、フレームワークソース以下に`compinit`がくるようにしてください。 ::: ::: details ZSH & Homebrew 下記コマンドで、`~/.zshrc`に`asdf.sh`を追加します: ```shell echo -e "\n. $(brew --prefix asdf)/libexec/asdf.sh" >> ${ZDOTDIR:-~}/.zshrc ``` **OR** use a ZSH Framework plugin like [asdf for oh-my-zsh](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/asdf) which will source this script and setup completions. コマンド補完は、ZSHフレームワーク`asdf`によって設定されるか、[Homebrewの説明に従って設定](https://docs.brew.sh/Shell-Completion#configuring-completions-in-zsh)必要があります。ZSHフレームワークを使用している場合、新しいZSHコマンド補完を使用するには、`fpath`経由で、関連する`asdf`プラグインの更新が必要となることがあります。Oh-My-ZSH asdfプラグインは、[ohmyzsh/ohmyzsh#8837](https://github.com/ohmyzsh/ohmyzsh/pull/8837)でご覧いただくと分かるとおり、まだ更新されていません。 ::: ::: details ZSH & Pacman `~/.zshrc`に下記の行を追記します: ```shell . /opt/asdf-vm/asdf.sh ``` コマンド補完は、ZSHに適した場所に配置されますが、[オートコンプリートを使用するようにZSHを設定する必要があります](https://wiki.archlinux.jp/index.php/Zsh#.E3.82.B3.E3.83.9E.E3.83.B3.E3.83.89.E8.A3.9C.E5.AE.8C)。 ::: ::: details PowerShell Core & Git `~/.config/powershell/profile.ps1`に下記の行を追記します: ```shell . "$HOME/.asdf/asdf.ps1" ``` ::: ::: details PowerShell Core & Homebrew 下記コマンドで、`~/.config/powershell/profile.ps1`に`asdf.sh`を追加します: ```shell 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 下記コマンドで、`~/.config/nushell/config.nu`に`asdf.nu`を追加します: ```shell "\n$env.ASDF_DIR = ($env.HOME | path join '.asdf')\n source " + ($env.HOME | path join '.asdf/asdf.nu') | save --append $nu.config-path ``` コマンド補完は自動的に設定されます。 ::: ::: details Nushell & Homebrew 下記コマンドで、`~/.config/nushell/config.nu`に`asdf.nu`を追加します: ```shell "\n$env.ASDF_DIR = (brew --prefix asdf | str trim | into string | path join 'libexec')\n source " + (brew --prefix asdf | str trim | into string | path join 'libexec/asdf.nu') | save --append $nu.config-path ``` コマンド補完は自動的に設定されます。 ::: ::: details Nushell & Pacman 下記コマンドで、`~/.config/nushell/config.nu`に`asdf.nu`を追加します: ```shell "\n$env.ASDF_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 下記コマンドで、`~/.profile`に`asdf.sh`を追加します: ```shell 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 など)を呼び出した**あと**に記述する必要があります。 ::: warning 警告 macOSでは、BashまたはZSHシェルを起動すると、自動的に`path_helper`というユーティリティが呼び出されます。`path_helper`は`PATH`(および`MANPATH`)内の項目の順番を並び替えることができるため、特定の順序を必要とするツールの動作に、一貫性が無くなってしまいます。これを回避するため、macOSで`asdf`を利用するときは、強制的に`PATH`エントリの先頭に追加する(優先度を一番高くする)ようにしてください。これは、`ASDF_FORCE_PREPEND`環境変数で制御できます。 ::: `PATH`の変更を反映するために、シェルを再起動してください。たいていの場合、ターミナルのタブを新たに開けばOKです。 ## コアのインストールが完了! これで、`asdf`のコアのインストールは完了です:tada: しかし、`asdf`が役に立つようになるのは、**プラグイン**をインストールしてから**ツール**をインストールし、**バージョン**を管理するようになってからです。引き続き、ガイドを進めていきましょう。 ## 4. プラグインのインストール ここではデモとして、[`asdf-nodejs`](https://github.com/asdf-vm/asdf-nodejs/)プラグインを使用して[Node.js](https://nodejs.org/)をインストール・設定してみましょう。 ### プラグインの依存関係 各プラグインには依存関係があるため、プラグインのリポジトリを確認しておきましょう。`asdf-nodejs`の場合、必要なものは次のとおりです: | OS | 依存関係インストールコマンド | | ------------------------------ | --------------------------------------- | | Debian | `apt-get install dirmngr gpg curl gawk` | | CentOS/ Rocky Linux/ AlmaLinux | `yum install gnupg2 curl gawk` | | macOS | `brew install gpg gawk` | 一部のプラグインではインストール後の事後処理でこれらの依存関係が必要となるため、あらかじめインストールしておきましょう。 ### プラグインのインストール ```shell asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git ``` ## 5. 特定のバージョンのインストール Node.js用のプラグインをインストールしたので、このツールの特定のバージョンをインストールしましょう。 インストール可能なバージョンは`asdf list all nodejs`コマンドで確認できますし、特定のメジャーバージョンのサブセットは`asdf list all nodejs 14`コマンドで確認できます。 最新版をインストールするには、次のコマンドを実行します: ```shell asdf install nodejs latest ``` ::: tip 備考 `asdf`では正確なバージョン番号を指定してください。`latest`は、現時点での最新バージョンを指定できる`asdf`のヘルパーです。 ::: ## 6. バージョンをセット `asdf`は、カレントディレクトリから上位の`$HOME`ディレクトリまでに存在するすべての`.tool-versions`ファイルをもとに、ツールのバージョンを照会します。照会は、`asdf`で管理するツールを実行した際に、ジャストインタイムで行われます。 ::: warning 警告 ツールで指定されたバージョンが見つからない場合、**エラー**が発生します。`asdf current`コマンドを実行すると、カレントディレクトリにおいてツールのバージョンを解決可能か確認できるため、どのツールが実行に失敗するか検証することができます。 ::: ### グローバル グローバルのデフォルト設定は、`$HOME/.tool-versions`で管理されます。グローバルのバージョンをセットするには、次のコマンドを実行します: ```shell asdf global nodejs latest ``` すると、`$HOME/.tool-versions`内には次のように書き込まれます: ``` nodejs 16.5.0 ``` 一部のOSでは、`python`のように、`asdf`ではなくシステムが管理するツールが既にインストールされていることがあります。それを使用する場合、`asdf`に対して、バージョン管理をシステムに委任するように指示する必要があります。詳しくは、[バージョンのリファレンス](/ja-jp/manage/versions.md)をご覧ください。 ### ローカル ローカルのバージョン設定は、`$PWD/.tool-versions`ファイル(カレントディレクトリ内)で定義されます。たいていの場合は、プロジェクトのGitリポジトリ内となるでしょう。対象となるディレクトリで、下記コマンドを実行します: ```shell asdf local nodejs latest ``` すると、`$PWD/.tool-versions`内には次のように書き込まれます: ``` nodejs 16.5.0 ``` ### ツールごとに用意された既存バージョンファイルの利用 `asdf`は、他のバージョンマネージャ向けに作られた既存のバージョンファイル(例: `rbenv`の場合は`.ruby-version`ファイル)からの移行をサポートしています。これはプラグイン単位でのサポートです。 [`asdf-nodejs`](https://github.com/asdf-vm/asdf-nodejs/)であれば、`.nvmrc`ファイルと`.node-version`ファイルの両方に対応しています。このサポートを有効にするには、`asdf`の構成設定ファイルである`$HOME/.asdfrc`内に、下記の行を追記してください: ``` legacy_version_file = yes ``` 構成設定でのその他のオプションについて詳しくは、[構成設定](/ja-jp/manage/configuration.md)のリファレンスをご覧ください。 ## 入門完了! 以上で、`asdf`の入門は完了です:tada: ここまでで、プロジェクトでの`nodejs`のバージョン管理ができるようになりました。プロジェクトで使用するツールごとに、同様の手順を実施してください! `asdf`には使いこなすと便利なコマンドが他にもいっぱいあり、`asdf --help`コマンドまたは単に`asdf`コマンドを実行すれば、すべてのコマンドの説明を見ることができます。コマンドは大きく分けて3つのカテゴリに分けられます: - [`asdf`のコア](/ja-jp/manage/core.md) - [プラグイン](/ja-jp/manage/plugins.md) - [ツールのバージョン](/ja-jp/manage/versions.md) ================================================ FILE: docs/ja-jp/guide/getting-started.md ================================================ # はじめよう ## 1. asdfのインストール asdfはいくつかの方法でインストールできます: ::: details パッケージマネージャーを使用 - **推奨** | パッケージマネージャー | コマンド | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Homebrew | `brew install asdf` | | Pacman | `git clone https://aur.archlinux.org/asdf-vm.git && cd asdf-vm && makepkg -si` または お好みの [AUR ヘルパー](https://wiki.archlinux.jp/index.php/AUR_%E3%83%98%E3%83%AB%E3%83%91%E3%83%BC) | ::: :::: details コンパイル済みバイナリをダウンロード - **かんたん** ##### asdfのインストール 1. https://github.com/asdf-vm/asdf/releases から、お使いのオペレーティングシステム/アーキテクチャの組み合わせに適したアーカイブをダウンロード。 2. アーカイブ内の`asdf`バイナリを`$PATH`のディレクトリに解凍。 3. `type -a asdf`を実行して、シェルの`$PATH`に`asdf`があることを確認します。`asdf`のバイナリを置いたディレクトリが`type`の出力の1行目に表示されるはずです。うまくいかない場合は、2の手順が正しく行えていない可能性があります。 :::: :::: details `go install` を使用 ##### asdfのインストール 1. [Goをインストールする](https://go.dev/doc/install) 2. コマンドを実行: `go install github.com/asdf-vm/asdf/cmd/asdf@v0.18.1` :::: :::: details ソースコードからビルドする ##### asdfのインストール 1. asdfリポジトリをクローン: ```shell git clone https://github.com/asdf-vm/asdf.git --branch v0.18.1 ``` 2. `make`を実行。 3. `asdf`バイナリを`$PATH`上のディレクトリに解凍。 4. `type -a asdf`を実行して、シェルの`$PATH`に`asdf`があることを確認します。`asdf`のバイナリを置いたディレクトリが`type`の出力の1行目に表示されるはずです。うまくいかない場合は、3の手順が正しく行えていない可能性があります。 :::: ## 2. asdfの設定 ::: tip 備考 ほとんどのユーザーは、asdfが管理するデータ(plugin, install, shim data)の保存先を変更する必要は**ありません**。ただし、デフォルトの`$HOME/.asdf`以外を指定したい場合は変更することができます。別のディレクトリを指定するには、シェルのRCファイルで`ASDF_DATA_DIR`変数をエクスポートしてください。 ::: シェル、OS、インストール方法には様々な組み合わせがあり、その全てがここでの設定に影響します。あなたのシステムに最も適したものを選んでください。 **masOSユーザーはこの節の最後にある`path_helper`に関する警告を必ず参照してください。** ::: details Bash **macOS Catalina以降**: デフォルトのシェルが**ZSH**に変更されました。Bashに変更していない限り、ZSHの手順を参照してください。 **Pacman**: コマンド補完が必要な場合は、[`bash-completion`](https://wiki.archlinux.jp/index.php/Bash#.E3.82.88.E3.81.8F.E4.BD.BF.E3.82.8F.E3.82.8C.E3.82.8B.E3.83.97.E3.83.AD.E3.82.B0.E3.83.A9.E3.83.A0.E3.81.A8.E3.82.AA.E3.83.97.E3.82.B7.E3.83.A7.E3.83.B3)をインストールしてください。 ##### shimsディレクトリをパスに追加する(必須) `~/.bash_profile`に以下を追記します: ```shell export PATH="${ASDF_DATA_DIR:-$HOME/.asdf}/shims:$PATH" ``` ###### カスタムデータディレクトリの設定(オプション) `~/.bash_profile`に以下を追記します(先述したパス追加よりも上の行に書くこと): ```shell export ASDF_DATA_DIR="/your/custom/data/dir" ``` ##### シェルのコマンド補完設定(オプション) `.bashrc`に以下を追記します: ```shell . <(asdf completion bash) ``` ::: ::: details Fish ##### shimsディレクトリをパスに追加する(必須) `~/.config/fish/config.fish`に以下を追記します: ```shell # ASDF configuration code if test -z $ASDF_DATA_DIR set _asdf_shims "$HOME/.asdf/shims" else 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 PATH if not contains $_asdf_shims $PATH set -gx --prepend PATH $_asdf_shims end set --erase _asdf_shims ``` ###### カスタムデータディレクトリの設定(オプション) **Pacman**: コマンド補完はAURパッケージのインストール時に自動的に設定されます。 `~/.config/fish/config.fish`に以下を追記します(先述したパス追加よりも上の行に書くこと): ```shell set -gx --prepend ASDF_DATA_DIR "/your/custom/data/dir" ``` ##### シェルのコマンド補完設定(オプション) コマンド補完は以下を実行して手動で設定する必要があります: ```shell $ asdf completion fish > ~/.config/fish/completions/asdf.fish ``` ::: ::: details Elvish ##### shimsディレクトリをパスに追加する(必須) `~/.config/elvish/rc.elv`に以下を追記します: ```shell var asdf_data_dir = ~'/.asdf' if (and (has-env ASDF_DATA_DIR) (!=s $E:ASDF_DATA_DIR '')) { set asdf_data_dir = $E:ASDF_DATA_DIR } if (not (has-value $paths $asdf_data_dir'/shims')) { set paths = [$path $@paths] } ``` ###### カスタムデータディレクトリの設定(オプション) カスタムデータディレクトリを設定するには、上記のスニペットの以下の行を変更してください: ```diff -var asdf_data_dir = ~'/.asdf' +var asdf_data_dir = '/your/custom/data/dir' ``` ##### シェルのコマンド補完設定(オプション) ```shell $ asdf completion elvish >> ~/.config/elvish/rc.elv $ echo "\n"'set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~' >> ~/.config/elvish/rc.elv ``` ::: ::: details ZSH **Pacman**: コマンド補完はZSHから使いやすい場所に配置されますが、[自動補完を使うにはZSHの設定で有効化する必要があります](https://wiki.archlinux.jp/index.php/Zsh#.E3.82.B3.E3.83.9E.E3.83.B3.E3.83.89.E8.A3.9C.E5.AE.8C)。 ##### shimsディレクトリをパスに追加する(必須) `~/.zshrc`に以下を追記します: ```shell export PATH="${ASDF_DATA_DIR:-$HOME/.asdf}/shims:$PATH" ``` ###### カスタムデータディレクトリの設定(オプション) `~/.zshrc`に以下を追記します(先述したパス追加よりも上の行に書くこと): ```shell export ASDF_DATA_DIR="/your/custom/data/dir" ``` ##### シェルのコマンド補完設定(オプション) コマンド補完はZSHフレームワークの`asdf`プラグイン([asdf for oh-my-zsh](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/asdf)のようなもの)を使用するか、以下の手順で設定します: ```shell $ mkdir -p "${ASDF_DATA_DIR:-$HOME/.asdf}/completions" $ asdf completion zsh > "${ASDF_DATA_DIR:-$HOME/.asdf}/completions/_asdf" ``` その場合は`.zshrc`に以下を追記します: ```shell # append completions to fpath fpath=(${ASDF_DATA_DIR:-$HOME/.asdf}/completions $fpath) # initialise completions with ZSH's compinit autoload -Uz compinit && compinit ``` **備考** ZSHフレームワークでカスタムされた`compinit`セットアップを使っている場合は、`compinit`がフレームワークのソース配下にあることを確認してください。 コマンド補完はZSHフレームワークの`asdf`で設定するか、[Homebrewの指示に従って設定](https://docs.brew.sh/Shell-Completion#configuring-completions-in-zsh)する必要があります。ZSHフレームワークを使っている場合、asdf用のプラグインを更新して`fpath`経由で新しいZSH補完機能を正しく使えるようにする必要があるかもしれません。なお、Oh-My-ZSH asdfプラグインはまだ対応していません。[ohmyzsh/ohmyzsh#8837](https://github.com/ohmyzsh/ohmyzsh/pull/8837) を参照してください。 ::: ::: details PowerShell Core ##### shimsディレクトリをパスに追加する(必須) `~/.config/powershell/profile.ps1`に以下を追記します: ```shell # Determine the location of the shims directory if ($null -eq $ASDF_DATA_DIR -or $ASDF_DATA_DIR -eq '') { $_asdf_shims = "${env:HOME}/.asdf/shims" } else { $_asdf_shims = "$ASDF_DATA_DIR/shims" } # Then add it to path $env:PATH = "${_asdf_shims}:${env:PATH}" ``` ###### カスタムデータディレクトリの設定(オプション) `~/.config/powershell/profile.ps1`に以下を追記します(先述したスニペットよりも上の行に書くこと): ```shell $env:ASDF_DATA_DIR = "/your/custom/data/dir" ``` PowerShellはコマンド補完に対応していません。 ::: ::: details Nushell ##### shimsディレクトリをパスに追加する(必須) `~/.config/nushell/config.nu`に以下を追記します: ```shell let shims_dir = ( if ( $env | get --ignore-errors ASDF_DATA_DIR | is-empty ) { $env.HOME | path join '.asdf' } else { $env.ASDF_DATA_DIR } | path join 'shims' ) $env.PATH = ( $env.PATH | split row (char esep) | where { |p| $p != $shims_dir } | prepend $shims_dir ) ``` ###### カスタムデータディレクトリの設定(オプション) `~/.config/nushell/config.nu`に以下を追記します(先述したパス追加よりも上の行に書くこと): ```shell $env.ASDF_DATA_DIR = "/your/custom/data/dir" ``` ##### シェルのコマンド補完設定(オプション) ```shell # If you've not customized the asdf data directory: $ mkdir $"($env.HOME)/.asdf/completions" $ asdf completion nushell | save $"($env.HOME)/.asdf/completions/nushell.nu" # If you have customized the data directory by setting ASDF_DATA_DIR: $ mkdir $"($env.ASDF_DATA_DIR)/completions" $ asdf completion nushell | save $"($env.ASDF_DATA_DIR)/completions/nushell.nu" ``` 次に、`~/.config/nushell/config.nu`に以下を追記します: ```shell let asdf_data_dir = ( if ( $env | get --ignore-errors ASDF_DATA_DIR | is-empty ) { $env.HOME | path join '.asdf' } else { $env.ASDF_DATA_DIR } ) . "$asdf_data_dir/completions/nushell.nu" ``` ::: ::: details POSIX Shell ##### shimsディレクトリをパスに追加する(必須) `~/.profile`に以下を追記します: ```shell export PATH="${ASDF_DATA_DIR:-$HOME/.asdf}/shims:$PATH" ``` ###### カスタムデータディレクトリの設定(オプション) `~/.profile`に以下を追記します(先述したパス追加よりも上の行に書くこと): ```shell export ASDF_DATA_DIR="/your/custom/data/dir" ``` ::: `asdf`のスクリプトは、`$PATH`を設定した**あと**、かつ、使用中のフレームワーク(oh-my-zsh など)を呼び出した**あと**に記述する必要があります。 `PATH`の変更を反映するために、シェルを再起動してください。たいていの場合、ターミナルのタブを新たに開けばOKです。 ## コアのインストールが完了! これで、`asdf`のコアのインストールは完了です :tada: しかし、`asdf`が役に立つようになるのは、**プラグイン**をインストールしてから**ツール**をインストールし、**バージョン**を管理するようになってからです。引き続き、ガイドを進めていきましょう。 ## 4. プラグインのインストール ここではデモとして、[`asdf-nodejs`](https://github.com/asdf-vm/asdf-nodejs/)プラグインを使用して[Node.js](https://nodejs.org/)をインストール・設定してみましょう。 ### プラグインの依存関係 各プラグインには依存関係があるため、プラグインのリポジトリを確認しておきましょう。`asdf-nodejs`の場合、必要なものは次のとおりです: | OS | 依存関係インストールコマンド | | ------------------------------ | --------------------------------------- | | Debian | `apt-get install dirmngr gpg curl gawk` | | CentOS/ Rocky Linux/ AlmaLinux | `yum install gnupg2 curl gawk` | | macOS | `brew install gpg gawk` | 一部のプラグインではインストール後の事後処理でこれらの依存関係が必要となるため、あらかじめインストールしておきましょう。 ### プラグインのインストール ```shell asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git ``` ## 5. 特定のバージョンのインストール Node.js用のプラグインをインストールしたので、このツールの特定のバージョンをインストールしましょう。 インストール可能なバージョンは`asdf list all nodejs`コマンドで確認できますし、特定のメジャーバージョンのサブセットは`asdf list all nodejs 14`コマンドで確認できます。 最新版をインストールするには、次のコマンドを実行します: ```shell asdf install nodejs latest ``` ::: tip 備考 `asdf`では正確なバージョン番号を指定してください。`latest`は、現時点での最新バージョンを指定できる`asdf`のヘルパーです。 ::: ## 6. バージョンをセット `asdf`は、カレントディレクトリから上位の`$HOME`ディレクトリまでに存在するすべての`.tool-versions`ファイルをもとに、ツールのバージョンを照会します。照会は、`asdf`で管理するツールを実行した際に、ジャストインタイムで行われます。 ::: warning 警告 ツールで指定されたバージョンが見つからない場合、**エラー**が発生します。`asdf current`コマンドを実行すると、カレントディレクトリにおいてツールのバージョンを解決可能か確認できるため、どのツールが実行に失敗するか検証することができます。 ::: asdfはまずカレントディレクトリにある `.tool-versions` ファイルを探し、見つからなければ親ディレクトリを参照し `.tool-versions` ファイルが見つかるまでファイルツリーの上位階層を探索します。`.tool-versions`ファイルが見つからない場合、バージョン解決処理は失敗し、エラーが表示されます。 すべてのディレクトリに適用されるデフォルトのバージョンを設定したい場合、`$HOME/.tool-versions`にバージョンを設定できます。特定のディレクトリで別のバージョンを設定しない限り、ホームディレクトリ以下のすべてのディレクトリに同じバージョンが設定されるようになります。 ```shell asdf set -u nodejs 16.5.0 ``` `$HOME/.tool-versions`は次のようになります: ``` nodejs 16.5.0 ``` 一部のOSでは、`python`のように、`asdf`ではなくシステムが管理するツールが既にインストールされていることがあります。それを使用する場合、`asdf`に対して、バージョン管理をシステムに委任するように指示する必要があります。詳しくは、[バージョンのリファレンス](/ja-jp/manage/versions.md)をご覧ください。 asdfが最初にバージョンを探す場所は、現在の作業ディレクトリ(`$PWD/.tool-versions`)です。これはプロジェクトのソースコードやGitリポジトリを含むディレクトリです。目的のディレクトリで`asdf set`を実行すると、バージョンを設定することができます: ```shell asdf set nodejs 16.5.0 ``` `$PWD/.tool-versions`は次のようになります: ``` nodejs 16.5.0 ``` ### ツールごとに用意された既存バージョンファイルの利用 `asdf`は、他のバージョンマネージャ向けに作られた既存のバージョンファイル(例: `rbenv`の場合は`.ruby-version`ファイル)からの移行をサポートしています。これはプラグイン単位でのサポートです。 [`asdf-nodejs`](https://github.com/asdf-vm/asdf-nodejs/)であれば、`.nvmrc`ファイルと`.node-version`ファイルの両方に対応しています。このサポートを有効にするには、`asdf`の構成設定ファイルである`$HOME/.asdfrc`内に、下記の行を追記してください: ``` legacy_version_file = yes ``` 構成設定でのその他のオプションについて詳しくは、[構成設定](/ja-jp/manage/configuration.md)のリファレンスをご覧ください。 ## 入門完了! 以上で、`asdf`の入門は完了です:tada: ここまでで、プロジェクトでの`nodejs`のバージョン管理ができるようになりました。プロジェクトで使用するツールごとに、同様の手順を実施してください! `asdf`には使いこなすと便利なコマンドが他にもいっぱいあり、`asdf --help`コマンドまたは単に`asdf`コマンドを実行すれば、すべてのコマンドの説明を見ることができます。コマンドは大きく分けて3つのカテゴリに分けられます: - [`asdf`のコア](/ja-jp/manage/core.md) - [プラグイン](/ja-jp/manage/plugins.md) - [ツールのバージョン](/ja-jp/manage/versions.md) ================================================ FILE: docs/ja-jp/guide/introduction.md ================================================ # イントロダクション `asdf`は、ツールのためのバージョンマネージャです。すべてのツールのバージョンは単一のファイル(`.tool-versions`)内で定義されるため、プロジェクトのGitリポジトリにチェックインして共有することで、チーム全員に同じーバージョンのツールを使ってもらえるようになります。 従来は、複数のCLIのバージョンマネージャを用意する必要があり、それぞれが異なるAPI、構成ファイル、および実装(`$PATH`の操作、Shim、環境変数など)となっていました。`asdf`は、開発ワークフローを簡素化するための単一インターフェースと構成ファイルを提供しており、シンプルなプラグインインターフェースを使って、すべてのツール・ランタイムに拡張することができます。 ## どうやって動いているの シェル上で`asdf`コアがセットアップすると、特定のツールを管理するためのプラグインをインストールすることができるようになります。プラグインによってツールがインストールされると、インストールされる実行ファイルごとに[Shim]()が作成されます。これらの実行ファイルを実行しようとすると、代わりにShimが実行され、`.tool-versions`で定義されているバージョンを`asdf`が認識して、当該バージョンが実行されます。 ## 関連プロジェクト ### nvm / n / rbenv etc [nvm](https://github.com/nvm-sh/nvm)、[n](https://github.com/tj/n)、および[rbenv](https://github.com/rbenv/rbenv)のようなツールはすべて、ツールによってインストールされる実行ファイルのShimを作成するシェルスクリプトです。 `asdf`はこれらのツールと非常に似ていて、ツール/ランタイムのバージョン管理という分野では競合しています。`asdf`はプラグインシステムを採用し、他のツールと差別化することで、ツール/ランタイムごとのマネージャ、マネージャごとの異なるコマンド、そしてリポジトリ内の様々な`*-version`ファイルといったものを排除しています。 ### direnv > シェルに、ディレクトリごとに環境変数をロード/アンロードできる機能を付け加えます。 `asdf`は環境変数を管理することはしませんが、direnvの動作を`asdf`に統合する[`asdf-direnv`](https://github.com/asdf-community/asdf-direnv)プラグインが存在します。 詳しくは[direnvのドキュメント](https://direnv.net/)をご覧ください。 ### Homebrew > macOS(またはLinux)のためのパッケージマネージャー Homebrewは、パッケージとその上位の依存関係を管理します。`asdf`は上位の依存関係を管理することはしませんし、パッケージマネージャでもありません。それらの管理はユーザが負担することとなりますが、`asdf`はなるべく依存関係のリストを小さく保つように努めています。 詳しくは[Homebrewのドキュメント](https://brew.sh/)をご覧ください。 ### NixOS > Nixは、パッケージ管理とシステム構成に独自のアプローチを取り入れたツールです。 NixOSは、各ツールの依存関係ツリー全体でパッケージのバージョンを正確に管理することで、真に再現可能な環境を構築することを目指しています。`asdf`でそのようなことはできません。NixOSは、独自のプログラミング言語、たくさんのCLIツール、そして60,000を超えるパッケージコレクションによって、それらの機能を支えています。 繰り返しになりますが、`asdf`は上位の依存関係を管理することはしませんし、パッケージマネージャでもありません。 詳しくは[NixOSのドキュメント](https://nixos.org/guides/how-nix-works.html)をご覧ください。 ## なぜasdfを使うの? `asdf`は、プラグインシステムによって**多くの**ツールをサポートしており、シェル構成にたった1行の**シェル**スクリプトを記述するだけで使えるというシンプルさ・親しみやすさによって、チームが**確実に**同じバージョンのツールを使用することを保証できます。 ::: tip Note `asdf`はシステムのパッケージマネージャになることを目指してはいません。あくまで、ツールのバージョンマネージャです。プラグインを作成することで、どのようなツールでも`asdf`で管理できるようになりますが、それがそのツールにとって最善の方法であるとは限りませんのでご注意ください。 ::: ================================================ FILE: docs/ja-jp/guide/parts/install-dependencies.md ================================================ ##### 依存関係のインストール asdfの動作には`git`が必要です。以下の表は、 _あなたが使用している_ パッケージマネージャで実行するコマンドの _一部例_ です(いくつかのツールは、後の手順で自動的にインストールされます)。 | OS | パッケージマネージャー | コマンド | | ----- | ------------------ | ----------------------------- | | linux | Aptitude | `apt install git` | | linux | DNF | `dnf install git` | | linux | Pacman | `pacman -S git` | | linux | Zypper | `zypper install git` | | macOS | Homebrew | `brew install coreutils git` | | macOS | Spack | `spack install coreutils git` | ::: tip 備考 お使いのシステムの構成によっては、`sudo`が必要となる場合があります。 ::: ================================================ FILE: docs/ja-jp/guide/upgrading-to-v0-16.md ================================================ # 0.16.0にアップグレードする asdfはバージョン0.15.0以前まではBashで書かれており、`asdf`関数がシェルにロードされたBashスクリプトのセットとして配布されていました。バージョン0.16.0はasdfをGoで完全に書き直したものです。完全に書き直したことによって多くの[破壊的変更](#breaking-changes)があり、現在ではスクリプトのセットではなくバイナリになっています。 ## インストール バージョン0.16.0のインストールは、以前のバージョンのasdfよりもはるかに簡単です。 たったの3ステップで完了します: * お使いのオペレーティングシステム/アーキテクチャの組み合わせに適した`asdf`バイナリをダウンロードし、`$PATH`のディレクトリに配置する。 * `$PATH`の前に`$ASDF_DATA_DIR/shims`を追加する。 * 以前にasdfのデータの保存場所をカスタマイズしていた場合は、`ASDF_DATA_DIR`に旧バージョンのプラグイン、バージョン、Shimをインストールしていたディレクトリを設定する(オプション)。 オペレーティングシステムのパッケージマネージャがすでにasdf 0.16.0を提供している場合は、それをインストールするのがおそらく最良の方法です。asdfのアップグレードはOSのパッケージマネージャーと手動インストールでのみ可能です。セルフアップグレード機能はありません。 ### データを失わずにアップグレードする 既存のインストールデータを失うことなく、asdfの最新バージョンにアップグレードできます。上記の手順と同じです。 #### 1. お使いのオペレーティングシステムとアーキテクチャに適した`asdf`バイナリをダウンロードする バイナリをダウンロードして、パスの通ったディレクトリに配置します。以下ではasdfのバイナリを`$HOME/bin`に配置し、`$HOME/bin`を`$PATH`の先頭に追加します: ``` # In .zshrc, .bashrc, etc... export PATH="$HOME/bin:$PATH" ``` #### 2. `ASDF_DATA_DIR` を設定する `asdf info`を実行し、変数`ASDF_DATA_DIR`を含む行をコピーします: ``` ... ASDF_DATA_DIR="/home/myuser/.asdf" ... ``` シェルのRCファイル(Zshの場合は`.zshrc`、Bashの場合は`.bashrc`など)で、`ASDF_DATA_DIR`を設定してエクスポートします: ```bash export ASDF_DATA_DIR="/home/myuser/.asdf" ``` #### 3. `$ASDF_DATA_DIR/shims`を`$PATH`の前に追加する。 シェルのRCファイル(手順2と同じファイル)で、パスの前に`$ASDF_DATA_DIR/shims`を追加します: ```bash export ASDF_DATA_DIR="/home/myuser/.asdf" export PATH="$ASDF_DATA_DIR/shims:$PATH" ``` #### 4. Shimを再生成する `asdf --help`を実行して、シェルセッションの`asdf`コマンドのバージョンが0.16.0以上であることを確認してください。まだ古いバージョンが表示されている場合は、新しいシェルセッションを開始する必要があります。 `asdf`コマンドが新しいバージョンであることを確認したら、`asdf reshim`を実行してすべてのShimを再生成します。この手順は、古いShimが古いバージョンのBashを参照している可能性があるため必要となります。 ### テスト 0.16.0へのアップグレードで問題が発生するかどうか確信が持てない場合は、「データを失わずにアップグレードする」で説明したように、既存のバージョンに加えて0.16.0をインストールしてテストすることができます。0.16.0へのアップグレードで不具合が生じることがわかったら、シェルのRCファイルに追加した行を削除してください。 ## 破壊的変更 ### ハイフン付きバージョンのコマンドは削除されました asdfバージョン0.15.0およびそれ以前では、特定のコマンドのハイフン付きバージョンとハイフンなしバージョンがサポートされていました。バージョン 0.16.0 では、ハイフンなしバージョンのみがサポートされます。影響を受けるコマンドは以下です: * `asdf list-all` -> `asdf list all` * `asdf plugin-add` -> `asdf plugin add` * `asdf plugin-list` -> `asdf plugin list` * `asdf plugin-list-all` -> `asdf plugin list all` * `asdf plugin-update` -> `asdf plugin update` * `asdf plugin-remove` -> `asdf plugin remove` * `asdf plugin-test` -> `asdf plugin test` * `asdf shim-versions` -> `asdf shimversions` ### `asdf global` と `asdf local` コマンドが `asdf set` に変更されました `asdf global` と `asdf local` は削除されました。「グローバル」と「ローカル」という用語は誤りで、誤解を招く恐れがありました。asdf は、実際にはあらゆる場所に適用される「グローバル」バージョンをサポートしていません。`asdf global`で指定したバージョンは、カレントディレクトリにある `.tool-versions` ファイルで簡単に上書きすることができました。これはユーザーを混乱させていました。 新しい`asdf set`はデフォルトでは`asdf local`と同じように動作しますが、ユーザーのホームディレクトリ (`--home`) や親ディレクトリ (`--parent`) にある既存の `.tool-versions` ファイルにバージョンを設定するためのフラグも用意されています。 この新しいインターフェイスは、asdfがどのようにバージョンを解決するかをよりよく理解し、同等の機能を提供することを期待しています。 ### `asdf update`コマンドが削除されました この方法ではアップデートを実行できなくなりました。OS のパッケージマネージャを使用するか、手動で最新のバイナリをダウンロードしてください。さらに、バージョン 0.15.0 以前の`asdf update`コマンドは、インストールプロセスが変更されたため、バージョン 0.16.0 にアップグレードできません。 **`asdf update`を使用して最新のGo実装にアップグレードすることはできません。** ### `asdf shell`コマンドが削除されました このコマンドは実際にユーザーの現在のシェルセッションに環境変数を設定していました。これは`asdf`が実行可能ファイルではなく、実際にはシェル関数であったために可能でした。新しい実装では、asdfからすべてのシェルコードが削除され、シェル関数ではなくバイナリになったので、シェルで直接環境変数を設定することはできなくなりました。 ### `asdf current`が変更されました これまで出力されていた3つのカラムの代わりに、最後のカラムはバージョンが設定されている場所か、バージョンを設定またはインストールするために実行可能な推奨コマンドのいずれかが表示されるようになりました。3番目のカラムが2つのカラムに分割されています。3番目のカラムは、バージョンが設定されている場合(通常、バージョンファイルか環境変数のいずれか)のみ、バージョンのソースを示すようになり、4番目は、指定されたバージョンが実際にインストールされているかどうかを示すブール値です。インストールされていない場合は、インストールコマンドの候補が表示されます。 ### プラグイン拡張コマンドの前に`cmd`を付ける必要があります 以前のプラグイン拡張コマンドは次のように実行できました: ``` asdf nodejs nodebuild --version ``` 現在では、組み込みコマンドと混同するのを避けるために、`cmd`をプレフィックスとして付ける必要があります: ``` asdf cmd nodejs nodebuild --version ``` ### 拡張コマンドが再設計されました プラグイン拡張コマンドにはいくつかの変更点があります: * 拡張コマンドは`exec`システムコールで実行可能でなければなりません。拡張コマンドがシェルスクリプトの場合、`exec`で実行するためには適切なshebang行で始まる必要があります。 * 拡張コマンドはバイナリでもスクリプトでも、どの言語でも実行できるようになりました。もはや`.bash`という拡張子を要求するのは誤解を招くので意味がありません。 * 実行権限が設定されていなければなりません。 * 実行権限がない場合、asdfによってBashスクリプトとしてソースされなくなりました。 さらに、プラグイン名の後の最初の引数のみが、実行する拡張コマンドを決定するために使用されます。これは、プラグイン名の後の最初の引数にマッチするコマンドが見つからない場合にasdfがデフォルトで実行する`command`拡張コマンドが存在することを意味します。たとえばこのようになります: ``` foo/ lib/commands/ command command-bar command-bat-man ``` 以前は、これらのスクリプトはこのように動作していました: ``` $ asdf cmd foo # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command` $ asdf cmd foo bar # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-bar` $ asdf cmd foo bat man # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-bat-man` ``` 現在はこのようになります: ``` $ asdf cmd foo # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command` $ asdf cmd foo bar # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-bar` $ asdf cmd foo bat man # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-bat man` ``` ### 実行可能なShimは`syscall.Exec`によって実行できる必要があります asdf 0.15.0以前はBashで実装されていたので、Bashで実行できる実行ファイルであれば実行することができました。つまり、shebang行がないスクリプトでも`asdf exec`で実行することができました。しかし、asdf 0.16.xがGoで実装されたことで、Goの`syscall.Exec`関数を使って実行ファイルを呼び出すようになり、shebangがないスクリプトは扱えなくなりました。 実際には、これはあまり問題ではありません。ほとんどのシェルスクリプトはshebang行を含んでいます。asdfによって管理されるツールがshebang行を持たないスクリプトを提供する場合、shebang行を追加する必要があります。 ### カスタムShimテンプレートはサポートされなくなりました これはめったに使われない機能でした。コアチームがメンテナンスしているプラグインでこの機能を使用していたのはElixirプラグインだけで、もう必要ありません。この機能はもともと、実行されるのではなくプログラムによって評価されるShimが、特定のプログラム(Elixir の場合は`iex`シェル)による評価に適したコードを含むように追加されました。さらに調べてみると、この機能は実行可能ファイルの`PATH`が不適切に設定され、選択されたバージョンの**実行可能ファイル**ではなく、**Shim**を含むように設定されていたために存在していたようです。 ================================================ FILE: docs/ja-jp/index.md ================================================ --- # https://vitepress.dev/reference/default-theme-home-page layout: home hero: name: asdf text: マルチランタイム
バージョンマネージャ tagline: 1つのツールですべてのランタイムのバージョンを管理しましょう! actions: - theme: brand text: はじめよう link: /ja-jp/guide/getting-started - theme: alt text: asdfってなに? link: /ja-jp/guide/introduction - theme: alt text: GitHubをみる link: https://github.com/asdf-vm/asdf features: - title: 単一ツール details: "単体のCLIツールとコマンドインターフェースで、各プロジェクトのランタイムを管理できます。" icon: 🎉 - title: プラグイン details: "既存ランタイム・ツールを使用した大規模なエコシステムです。必要に応じて新しいツールをサポートできるシンプルなAPIを用意しています!" icon: 🔌 - title: 後方互換性 details: ".nvmrc、.node-version、.ruby-versionといった既存構成ファイルから、スムーズに移行できます!" icon: ⏮ - title: "単一の構成ファイル" details: ".tool-versionsを使用すると、すべてのツール、ランタイム、およびそれらのバージョンを、共有された単一の場所で管理できます。" icon: 📄 - title: "シェル" details: "Bash、ZSH、Fish、およびElvishをサポートし、コマンド補完にも対応しています。" icon: 🐚 - title: "GitHub Actions" details: "CI/CDワークフローで、.tool-versionsをインストールし利用するためのGitHub Actionを提供しています。" icon: 🤖 --- ================================================ FILE: docs/ja-jp/manage/commands.md ================================================ # すべてのコマンド `asdf`で利用可能なすべてのコマンドの一覧です。この一覧は、`asdf help`コマンドで表示されるテキストです。 <<< @../../internal/help/help.txt ================================================ FILE: docs/ja-jp/manage/configuration.md ================================================ # 構成設定 `asdf`の構成設定には、他人と共有可能な`.tool-versions`ファイルと、`.asdfrc`や環境変数によってカスタマイズ可能なユーザ固有の設定とがあります。 ## `.tool-versions` `.tool-versions`ファイルがディレクトリに存在する場合、当該ディレクトリおよびサブディレクトリで、ファイル内で宣言しているツールのバージョンが使用されます。 `.tool-versions`ファイル内は下記のような記述となっています: ``` ruby 2.5.3 nodejs 10.15.0 ``` コメントを含めることもできます: ``` ruby 2.5.3 # This is a comment # This is another comment nodejs 10.15.0 ``` バージョンの表記は下記の形式があります: - `10.15.0` - 実バージョンの表記です。バイナリのダウンロードに対応しているプラグインの場合、バイナリがダウンロードされます。 - `ref:v1.0.2-a` or `ref:39cb398vb39` - 指定されたタグ/コミット/ブランチをgithubからダウンロードし、コンパイルされます。 - `path:~/src/elixir` - 使用するツールをカスタムコンパイルしたバージョンへのパスです。言語開発者などが使用します。 - `system` - このキーワードを指定した場合、asdfが管理していない、システム上のツールバージョンへパススルーします。 ::: tip ヒント スペースで区切れば、複数のバージョンを指定できます。例えば、Python `3.7.2`を使用し、Python `2.7.15`にフォールバックし、最終的に`system`のPythonにフォールバックさせるには、`.tool-versions`に下記の行を追記します。 ``` python 3.7.2 2.7.15 system ``` ::: `.tool-versions`ファイルで定義されているすべてのツールをインストールするには、`.tool-versions`ファイルを含むディレクトリで、`asdf install`コマンドを引数を指定せずに実行します。 `.tool-versions`ファイルで定義されている単一のツールをインストールするには、`.tool-versions`ファイルを含むディレクトリで、`asdf install `コマンドを実行します。ツールは、`.tool-versions`ファイルで指定されたバージョンでインストールされます。 ファイルは、直接編集するか、`asdf set`コマンドを使用して更新してください。 ## `.asdfrc` `.asdfrc`では、ユーザのマシン固有の構成を設定します。 asdfはデフォルトで`${HOME}/.asdfrc`に構成ファイルを配置します。ファイルの場所は、[`ASDF_CONFIG_FILE`環境変数](#asdf-config-file)で設定できます。 下記は、構成に必要な項目とそのデフォルト値を示しています: ```txt legacy_version_file = no use_release_candidates = no always_keep_download = no plugin_repository_last_check_duration = 60 disable_plugin_short_name_repository = no concurrency = auto ``` ### `legacy_version_file` **対応している**プラグインの場合、他のバージョンマネージャで使用されているバージョンファイルを読み込むことができます。例えば、Rubyの`rbenv`であれば`.ruby-version`ファイルを読み込みます。 | オプション | 説明 | | :------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------- | | `no` | バージョンの読み込みには`.tool-versions`を使用します | | `yes` | 利用可能なレガシーバージョンファイル(`.ruby-version`など)がある場合、プラグインのフォールバックで使用します | ### `always_keep_download` `asdf install`コマンドでダウンロードしたソースコードやバイナリを、保持しておくか削除するかを制御します。 | オプション | 説明 | | :------------------------------------------------------------ | :----------------------------------------------------------- | | `no` | インストールが成功したら、ソースコードやバイナリを削除します | | `yes` | インストール後も、ソースコードやバイナリを保持します | ### `plugin_repository_last_check_duration` asdfプラグインリポジトリの同期間隔(分)を制御します。何らかのトリガーイベントが発生した際に、最後に同期した時刻からの経過時間をチェックします。設定された間隔以上の時間が経過していた倍は、新たに同期が開始されます。 | オプション | 説明 | | :-------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------- | | `1`から`999999999`までの整数値
は`60` | 最後に同期した時刻から指定時間(分)以上経過していた場合、トリガーイベントで同期します | | `0` | トリガーイベントのたびに同期します | | `never` | 同期しません | 同期は、以下のコマンドが実行されたときに発生します: - `asdf plugin add ` - `asdf plugin list all` `asdf plugin add `コマンドでは、プラグインの同期はトリガーされません。 ::: warning 備考 値を`never`にしても、プラグインリポジトリの初期同期は停止されません。この動作については、`disable_plugin_short_name_repository`の節をご覧ください。 ::: ### `disable_plugin_short_name_repository` asdfプラグインのショートネームリポジトリの同期を無効化します。ショートネームリポジトリが無効となっている場合、同期イベントはすぐに終了します。 | オプション | 説明 | | :------------------------------------------------------------ | :----------------------------------------------------------------------------- | | `no` | 同期イベントが発生した際に、asdfプラグインリポジトリをクローンまたは更新します | | `yes` | プラグインショートネームリポジトリを無効化します | 同期は、以下のコマンドが実行されたときに発生します: - `asdf plugin add ` - `asdf plugin list all` `asdf plugin add `コマンドでは、プラグインの同期はトリガーされません。 ::: warning 備考 プラグインショートネームリポジトリを無効化しても、すでに同期されたリポジトリは削除されません。プラグインリポジトリを削除するには、`rm --recursive --trash $ASDF_DATA_DIR/repository`コマンドを実行してください。 また、プラグインショートネームリポジトリを無効化しても、以前にこのソースからインストールされたプラグインは削除されません。プラグインを削除するには、`asdf plugin remove `コマンドを実行してください。プラグインを削除すると、そのプラグインでインストールされたすべてのツールバージョンが削除されます。 ::: ### `concurrency` コンパイル時に使用するデフォルトのコア数です。 | Options | Description | | :------ | :---------------------------------------------------------------------------------- | | 整数値 | ソースコードのコンパイル時に使用するコア数です | | `auto` | `nproc`、`sysctl hw.ncpu`、`/proc/cpuinfo`、または`1`、の優先順でコア数を計算します | 備考: `ASDF_CONCURRENCY`環境変数が設定されている場合はそちらが優先されます。 ### プラグインフック 下記のタイミングで、カスタムコードを実行することができます: - プラグインのインストール、Shim再生成、更新および削除をする際の前後 - プラグインコマンドの実行前後 例えば、`foo`というプラグインがインストールされていて、`bar`という実行可能ファイルが提供されている場合、以下のようなフックを使うことで、一番最初にカスタムコードを実行することができます: ```text pre_foo_bar = echo Executing with args: $@ ``` 以下のパターンがサポートされています: - `pre__` - `pre_asdf_download_` - `{pre,post}_asdf_{install,reshim,uninstall}_` - `$1`: フルバージョン - `{pre,post}_asdf_plugin_{add,update,remove,reshim}` - `$1`: プラグイン名 - `{pre,post}_asdf_plugin_{add,update,remove}_` どのようなコマンドの前後にどのようなコマンドフックを実行すべきかについての詳細は、[プラグインの作成](../plugins/create.md)をご覧ください。 ## 環境変数 環境変数の設定値は、お使いのシステムやシェルによって異なります。デフォルトロケーションは、インストールした場所や方法(Gitクローン、Homebrew、AUR)によって異なります。 環境変数は通常、`asdf.sh`/`asdf.fish`などをsourceする前に設定する必要があります。Elvishの場合は、`use asdf`の上側に設定します。 以下では、Bashシェルでの使用方法について説明します。 ### `ASDF_CONFIG_FILE` `.asdfrc`構成ファイルへのパスです。任意の場所に設定できます。必ず絶対パスで設定してください。 - 未設定の場合: `$HOME/.asdfrc`の値が使用されます。 - 使用方法: `export ASDF_CONFIG_FILE=/home/john_doe/.config/asdf/.asdfrc` ### `ASDF_TOOL_VERSIONS_FILENAME` ツール名とバージョンの情報を格納するファイルのファイル名です。有効なファイル名であれば何でも設定できます。通常、`.tool-versions`ファイルを無視したい場合を除き、この値を変更するべきではありません。 - 未設定の場合: `.tool-versions`の値が使用されます。 - 使用方法: `export ASDF_TOOL_VERSIONS_FILENAME=tool_versions` ### `ASDF_DIR` `asdf`のコアスクリプト場所です。任意の場所に設定できます。必ず絶対パスで設定してください。 - 未設定の場合: `bin/asdf`実行ファイルの親ディレクトリが使用されます。 - 使用方法: `export ASDF_DIR=/home/john_doe/.config/asdf` ### `ASDF_DATA_DIR` `asdf`がプラグイン、Shim、ツールのバージョンをインストールする場所です。任意の場所に設定できます。必ず絶対パスで設定してください。 - 未設定の場合: `$HOME/.asdf`ディレクトリが存在すればその場所、存在しない場合は`ASDF_DIR`の値を使用します。 - 使用方法: `export ASDF_DATA_DIR=/home/john_doe/.asdf` ### `ASDF_CONCURRENCY` ソースコードのコンパイル時に使用するコア数です。この環境変数の値は、asdf構成ファイルの`concurrency`の値よりも優先されます。 - 未設定の場合: asdf構成ファイルの`concurrency`の値が使用されます。 - 使用方法: `export ASDF_CONCURRENCY=32` ## 完全な構成の例 下記のように、asdfをシンプルにセットアップしたとします: - Bashシェル - インストール先は`$HOME/.asdf` - Git経由でインストール - 環境変数は何も設定していない - `.asdfrc`ファイルは何もカスタマイズしていない すると、結果として以下のような構成となります: | 構成 | 値 | 値がセットされる過程 | | :------------------------------------ | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------ | | config file location | `$HOME/.asdfrc` | `ASDF_CONFIG_FILE`は空なので、`$HOME/.asdfrc`が使用されます。 | | default tool versions filename | `.tool-versions` | `ASDF_TOOL_VERSIONS_FILENAME`は空なので、`.tool-versions`が使用されます。 | | asdf dir | `$HOME/.asdf` | `ASDF_DIR`は空なので、`bin/asdf`の親ディレクトリが使用されます。 | | asdf data dir | `$HOME/.asdf` | `ASDF_DATA_DIR`は空であり、`$HOME`が存在するので、`$HOME/.asdf`が使用されます。 | | concurrency | `auto` | `ASDF_CONCURRENCY`は空なので、[デフォルト構成](https://github.com/asdf-vm/asdf/blob/master/defaults)の`concurrency`の値に依存します。 | | legacy_version_file | `no` | `.asdfrc`をカスタマイズしていないので、[デフォルト構成](https://github.com/asdf-vm/asdf/blob/master/defaults)を使用します。 | | use_release_candidates | `no` | `.asdfrc`をカスタマイズしていないので、[デフォルト構成](https://github.com/asdf-vm/asdf/blob/master/defaults)を使用します。 | | always_keep_download | `no` | `.asdfrc`をカスタマイズしていないので、[デフォルト構成](https://github.com/asdf-vm/asdf/blob/master/defaults)を使用します。 | | plugin_repository_last_check_duration | `60` | `.asdfrc`をカスタマイズしていないので、[デフォルト構成](https://github.com/asdf-vm/asdf/blob/master/defaults)を使用します。 | | disable_plugin_short_name_repository | `no` | `.asdfrc`をカスタマイズしていないので、[デフォルト構成](https://github.com/asdf-vm/asdf/blob/master/defaults)を使用します。 | ================================================ FILE: docs/ja-jp/manage/core.md ================================================ # コア コアとなる`asdf`のコマンドはかなり少量ですが、多くのワークフローを円滑に進めることができます。 ## インストール & セットアップ [はじめよう](/ja-jp/guide/getting-started.md)のガイドで説明されています。 ## 実行 ```shell asdf exec [args...] ``` 現在のバージョンのShimでコマンドを実行します。 ## 環境変数 ```shell asdf env [util] ``` ## 情報 ```shell asdf info ``` OS、シェル、および`asdf`のデバッグ情報を表示するヘルパーコマンドです。バグレポート作成時に共有してください。 ## Shimの再作成 ```shell asdf reshim ``` 特定のパッケージ・バージョンのShimを再作成します。デフォルトでは、Shimはプラグインによってツールのインストール中に作成されます。[npm CLI](https://docs.npmjs.com/cli/)などのツールは、実行ファイルをグローバルインストールができます(例:`npm install -g yarn`コマンドで[Yarn](https://yarnpkg.com/)をインストール)が、これらの実行ファイルはプラグインのライフサイクルを通してインストールされないため、Shimはまだ存在しません。そのような時に、例えば`asdf reshim nodejs `を実行すると、`nodejs`の``に対して、`yarn`のような新しい実行ファイルのShimを強制的に再作成させることができます。 ## Shimのバージョン ```shell asdf shimversions ``` ``のShimを提供するプラグインおよびバージョンを一覧で表示します。 例えば、[Node.js](https://nodejs.org/)には`node`と`npm`という2つの実行ファイルが提供されています。[`asdf-nodejs`](https://github.com/asdf-vm/asdf-nodejs/)プラグインで、複数のバージョンのツールがインストールされている場合、`shimversions`は下記のような一覧を返します: ```shell ➜ asdf shimversions node nodejs 14.8.0 nodejs 14.17.3 nodejs 16.5.0 ``` ```shell ➜ asdf shimversions npm nodejs 14.8.0 nodejs 14.17.3 nodejs 16.5.0 ``` ## 更新 `asdf` をインストールするときと同じ方法で更新してください。このページの右上に asdf の最新バージョンが表示されます。 ## アンインストール `asdf`をアンインストールするには以下の手順に従ってください: ::: details Bash & Git 1. `~/.bashrc`で、`asdf.sh`およびコマンド補完をsourceしている行を削除します: ```shell . "$HOME/.asdf/asdf.sh" . "$HOME/.asdf/completions/asdf.bash" ``` 2. `$HOME/.asdf`ディレクトリを削除します: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 3. `asdf`のすべての構成ファイルを削除するために次のコマンドを実行します: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Bash & Git (macOS) 1. `~/.bash_profile`で、`asdf.sh`およびコマンド補完をsourceしている行を削除します: ```shell . "$HOME/.asdf/asdf.sh" . "$HOME/.asdf/completions/asdf.bash" ``` 2. `$HOME/.asdf`ディレクトリを削除します: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 3. `asdf`のすべての構成ファイルを削除するために次のコマンドを実行します: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Bash & Homebrew 1. `~/.bashrc`で、`asdf.sh`およびコマンド補完をsourceしている行を削除します: ```shell . $(brew --prefix asdf)/libexec/asdf.sh . $(brew --prefix asdf)/etc/bash_completion.d/asdf.bash ``` コマンド補完については、[Homebrewで説明されている方法で設定](https://docs.brew.sh/Shell-Completion#configuring-completions-in-bash)されている可能性があるため、そちらのガイドに従って削除する行を見つけてください。 2. パッケージマネージャでアンインストールします: ```shell brew uninstall asdf --force ``` 3. `asdf`のすべての構成ファイルを削除するために次のコマンドを実行します: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Bash & Homebrew (macOS) **macOS Catalina以降**では、デフォルトのシェルが**ZSH**に変更されました。もし、`~/.bash_profile`に設定が見つからない場合は、`~/.zshrc`にある可能性があります。その場合は、ZSHの手順をご覧ください。 1. `~/.bash_profile`で、`asdf.sh`およびコマンド補完をsourceしている行を削除します: ```shell . $(brew --prefix asdf)/libexec/asdf.sh . $(brew --prefix asdf)/etc/bash_completion.d/asdf.bash ``` コマンド補完については、[Homebrewで説明されている方法で設定](https://docs.brew.sh/Shell-Completion#configuring-completions-in-bash)されている可能性があるため、そちらのガイドに従って削除する行を見つけてください。 2. パッケージマネージャでアンインストールします: ```shell brew uninstall asdf --force ``` 3. `asdf`のすべての構成ファイルを削除するために次のコマンドを実行します: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Bash & Pacman 1. `~/.bashrc`で、`asdf.sh`およびコマンド補完をsourceしている行を削除します: ```shell . /opt/asdf-vm/asdf.sh ``` 2. パッケージマネージャでアンインストールします: ```shell pacman -Rs asdf-vm ``` 3. `$HOME/.asdf`ディレクトリを削除します: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 4. `asdf`のすべての構成ファイルを削除するために次のコマンドを実行します: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Fish & Git 1. `~/.config/fish/config.fish`で、`asdf.fish`をsourceしている行を削除します: ```shell source ~/.asdf/asdf.fish ``` そして、次のコマンドで、コマンド補完を削除します: ```shell rm -rf ~/.config/fish/completions/asdf.fish ``` 2. `$HOME/.asdf`ディレクトリを削除します: ```shell rm -rf (string join : -- $ASDF_DATA_DIR $HOME/.asdf) ``` 3. `asdf`のすべての構成ファイルを削除するために次のコマンドを実行します: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Fish & Homebrew 1. `~/.config/fish/config.fish`で、`asdf.fish`をsourceしている行を削除します: ```shell source "(brew --prefix asdf)"/libexec/asdf.fish ``` 2. パッケージマネージャでアンインストールします: ```shell brew uninstall asdf --force ``` 3. `asdf`のすべての構成ファイルを削除するために次のコマンドを実行します: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Fish & Pacman 1. `~/.config/fish/config.fish`で、`asdf.fish`をsourceしている行を削除します: ```shell source /opt/asdf-vm/asdf.fish ``` 2. パッケージマネージャでアンインストールします: ```shell pacman -Rs asdf-vm ``` 3. `$HOME/.asdf`ディレクトリを削除します: ```shell rm -rf (string join : -- $ASDF_DATA_DIR $HOME/.asdf) ``` 4. `asdf`のすべての構成ファイルを削除するために次のコマンドを実行します: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Elvish & Git 1. `~/.config/elvish/rc.elv`で、`asdf`モジュールを使用している行を削除します: ```shell use asdf _asdf; var asdf~ = $_asdf:asdf~ set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~ ``` そして、次のコマンドで、`asdf`モジュールを削除します: ```shell rm -f ~/.config/elvish/lib/asdf.elv ``` 2. `$HOME/.asdf`ディレクトリを削除します: ```shell if (!=s $E:ASDF_DATA_DIR "") { rm -rf $E:ASDF_DATA_DIR } else { rm -rf ~/.asdf } ``` 3. `asdf`のすべての構成ファイルを削除するために次のコマンドを実行します: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Elvish & Homebrew 1. `~/.config/elvish/rc.elv`で、`asdf`モジュールを使用している行を削除します: ```shell use asdf _asdf; var asdf~ = $_asdf:asdf~ set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~ ``` そして、次のコマンドで、`asdf`モジュールを削除します: ```shell rm -f ~/.config/elvish/lib/asdf.elv ``` 2. パッケージマネージャでアンインストールします: ```shell brew uninstall asdf --force ``` 3. `asdf`のすべての構成ファイルを削除するために次のコマンドを実行します: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Elvish & Pacman 1. `~/.config/elvish/rc.elv`で、`asdf`モジュールを使用している行を削除します: ```shell use asdf _asdf; var asdf~ = $_asdf:asdf~ set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~ ``` そして、次のコマンドで、`asdf`モジュールを削除します: ```shell rm -f ~/.config/elvish/lib/asdf.elv ``` 2. パッケージマネージャでアンインストールします: ```shell pacman -Rs asdf-vm ``` 3. `$HOME/.asdf`ディレクトリを削除します: ```shell if (!=s $E:ASDF_DATA_DIR "") { rm -rf $E:ASDF_DATA_DIR } else { rm -rf ~/.asdf } ``` 4. `asdf`のすべての構成ファイルを削除するために次のコマンドを実行します: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details ZSH & Git 1. `~/.zshrc`で、`asdf.sh`およびコマンド補完をsourceしている行を削除します: ```shell . "$HOME/.asdf/asdf.sh" # ... fpath=(${ASDF_DIR}/completions $fpath) autoload -Uz compinit compinit ``` **または**、ZSHフレームワークプラグインを使用します。 2. `$HOME/.asdf`ディレクトリを削除します: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 3. `asdf`のすべての構成ファイルを削除するために次のコマンドを実行します: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details ZSH & Homebrew 1. `~/.zshrc`で、`asdf.sh`をsourceしている行を削除します: ```shell . $(brew --prefix asdf)/libexec/asdf.sh ``` 2. パッケージマネージャでアンインストールします: ```shell brew uninstall asdf --force && brew autoremove ``` 3. `asdf`のすべての構成ファイルを削除するために次のコマンドを実行します: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details ZSH & Pacman 1. `~/.zshrc`で、`asdf.sh`をsourceしている行を削除します: ```shell . /opt/asdf-vm/asdf.sh ``` 2. パッケージマネージャでアンインストールします: ```shell pacman -Rs asdf-vm ``` 3. `$HOME/.asdf`ディレクトリを削除します: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 4. `asdf`のすべての構成ファイルを削除するために次のコマンドを実行します: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: たったこれだけです! 🎉 ================================================ FILE: docs/ja-jp/manage/plugins.md ================================================ # プラグイン プラグインは、`asdf`がNode.jsやRuby、Elixirなどの様々なツールを取り扱えるようにするためのものです。 様々なツールをサポートするために使用されるプラグインAPIについては、[プラグインの作成](/ja-jp/plugins/create.md)をご覧ください。 ## 追加 下記コマンドでは、GitのURLからプラグインを追加します: ```shell asdf plugin add # asdf plugin add elm https://github.com/vic/asdf-elm ``` または下記のコマンドで、プラグインリポジトリのショートネームを指定して追加します: ```shell asdf plugin add # asdf plugin add erlang ``` ::: tip 推奨 リポジトリのショートネームに依存しないために、`git-url`を使用することを推奨します。 ::: ## インストール済みプラグイン一覧 ```shell asdf plugin list # asdf plugin list # java # nodejs ``` ```shell asdf plugin list --urls # asdf plugin list # java https://github.com/halcyon/asdf-java.git # nodejs https://github.com/asdf-vm/asdf-nodejs.git ``` ## 全プラグインのショートネーム一覧 ```shell asdf plugin list all ``` 全プラグインのショートネーム一覧については、[プラグインショートネームの一覧](https://github.com/asdf-vm/asdf-plugins)もご覧ください。 ## 更新 ```shell asdf plugin update --all ``` 特定のプラグインパッケージを更新したい場合は、下記のように指定してください。 ```shell asdf plugin update # asdf plugin update erlang ``` この更新コマンドは、プラグインリポジトリの _origin_ の _デフォルトブランチ_ における _最新コミット_ を取得します。バージョニングされたプラグインの更新機能については、現在開発中です([#916](https://github.com/asdf-vm/asdf/pull/916))。 ## 削除 ```bash asdf plugin remove # asdf plugin remove erlang ``` プラグインを削除すると、当該プラグインでインストールされたすべてのツールが削除されます。これは、各ツールの未使用バージョンを手っ取り早くクリーンアップ/プルーニングするのに有用です。 ## ショートネームリポジトリの同期 ショートネームリポジトリはローカルマシンに同期され、定期的に更新されます。同期のタイミングの条件は、次のとおりです: - 同期イベントは、下記コマンドによってトリガーされます: - `asdf plugin add ` - `asdf plugin list all` - 構成設定の`disable_plugin_short_name_repository`オプションが`yes`の場合、同期は中止されます。詳しくは[asdfの構成設定](/ja-jp/manage/configuration.md)のリファレンスをご覧ください。 - もし、過去`X`分の間に同期が行われていない場合、同期が開始されます。 - `X`のデフォルト値は`60`ですが、`.asdfrc`ファイルの`plugin_repository_last_check_duration`オプションで変更することができます。詳しくは[asdfの構成設定](/ja-jp/manage/configuration.md)のリファレンスをご覧ください。 ================================================ FILE: docs/ja-jp/manage/versions.md ================================================ # バージョン ## バージョンのインストール ```shell asdf install # asdf install erlang 17.3 ``` プラグインが、ソースコードからのダウンロード・コンパイルをサポートしている場合、`ref:foo`の形式(`foo`は特定のブランチ、タグ、またはコミット)でバージョンを指定できます。アンインストールするときも、同じ名前とバージョンを指定する必要があります。 ## 最新安定バージョンのインストール ```shell asdf install latest # asdf install erlang latest ``` 下記のように、特定のプレフィックスでの最新安定バージョンをインストールすることもできます。 ```shell asdf install latest: # asdf install erlang latest:17 ``` ## インストール済みバージョン一覧 ```shell asdf list # asdf list erlang ``` 下記のように、特定のプレフィックスでのバージョンでフィルタすることもできます。 ```shell asdf list # asdf list erlang 17 ``` ## インストール可能な全バージョン一覧 ```shell asdf list all # asdf list all erlang ``` 下記のように、特定のプレフィックスでのバージョンでフィルタすることもできます。 ```shell asdf list all # asdf list all erlang 17 ``` ## 最新安定バージョンの表示 ```shell asdf latest # asdf latest erlang ``` 下記のように、特定のプレフィックスでの最新安定バージョンで表示することもできます。 ```shell asdf latest # asdf latest erlang 17 ``` ## バージョンのセット #### `.tool-versions`ファイルで管理する ```shell asdf set [flags] [...] # asdf set elixir 1.2.4 # set in current dir # asdf set -u elixir 1.2.4 # set in .tool-versions file in home directory # asdf set -p elixir 1.2.4 # set in existing .tool-versions file in a parent dir asdf set latest[:] # asdf set elixir latest ``` `asdf set`はカレントディレクトリの`.tool-versions`ファイルにバージョンを書き込みます。これは単純に利便性のために存在します。`echo " " > .tool-versions`を実行するようなものと考えてください。 `u` または `--home`フラグをつけて`asdf set`を実行すると、`$HOME`ディレクトリの`.tool-versions`ファイルにバージョンを書き込みます。ファイルが存在しない場合は作成されます。 `p`または `--parent`フラグをつけて`asdf set`を実行すると、カレントディレクトリから親ディレクトリを探索し、最初に見つかった`.tool-versions` ファイルにバージョンを書き込みます。 #### 環境変数で管理する バージョンを決定するときに、`ASDF_${TOOL}_VERSION`というパターンの環境変数を探します。バージョンの形式は`.tool-versions`ファイルでサポートされているものと同じです。設定されている場合、この環境変数の値は`.tool-versions`ファイルでのバージョン指定よりも優先されます。たとえば: ```shell export ASDF_ELIXIR_VERSION=1.18.1 ``` これは現在のシェルセッションではElixir `1.18.1`を使うようasdfに指示します。 :::warning これは環境変数なので、設定されたセッションでのみ有効になります。 実行中の他のセッションでは、`.tool-versions`ファイルに設定されているバージョンを引き続き使用します。 `.tool-versions`ファイルについて詳しくは、[構成設定のリファレンス](/ja-jp/manage/configuration.md)をご覧ください。 ::: 下記の例では、バージョン`1.4.0`のElixirプロジェクトに対して、テストを実行させています。 ```shell ASDF_ELIXIR_VERSION=1.4.0 mix test ``` ## システムバージョンへの委任 asdfで管理されているバージョンではなく、``で指定されたツールのシステムバージョンを使用するには、バージョンとして`system`を指定します。 [バージョンのセット](#バージョンのセット)と同様に、`asdf set`または環境変数で`system`をセットしてください。 ```shell asdf set system # asdf set python system ``` ## カレントバージョンの表示 ```shell asdf current # asdf current # erlang 17.3 /Users/kim/.tool-versions # nodejs 6.11.5 /Users/kim/cool-node-project/.tool-versions asdf current # asdf current erlang # erlang 17.3 /Users/kim/.tool-versions ``` ## バージョンのアンインストール ```shell asdf uninstall # asdf uninstall erlang 17.3 ``` ## Shims asdfがパッケージをインストールすると、そのパッケージに含まれるすべての実行プログラムのShimが`$ASDF_DATA_DIR/shims`ディレクトリ(デフォルトは`~/.asdf/shims`)に作成されます。このディレクトリが(`asdf.sh`や`asdf.fish`などによって)`$PATH`に設定されることで、インストールされているプログラムが当該環境で利用できるようになります。 Shim自体は非常に単純なラッパーであり、`asdf exec`というヘルパープログラムに、プラグイン名と、Shimがラップしているインストール済みパッケージの実行ファイルのパスを渡して、`exec`します。 `asdf exec`ヘルパーは、使用するパッケージのバージョン(`.tool-versions`ファイルで指定されたもの、または環境変数で指定されたもの)、パッケージのインストールディレクトリにある実行ファイルの完全パス(プラグインの`exec-path`コールバックで操作可能)、および実行環境(プラグインの`exec-env`スクリプトで提供)を決定し、実行します。 ::: warning 備考 本システムは`exec`呼び出しを使用するため、シェルによってsourceされるパッケージ内のスクリプトは、Shimラッパーを経由させずに直接アクセスする必要があります。`asdf`で用意されている`which`および`where`コマンドは、下記のように、インストールされたパッケージへのパスを返すため、この状況を解決するのに役立ちます: ::: ```shell # returns path to main executable in current version source $(asdf which ${PLUGIN})/../script.sh # returns path to the package installation directory source $(asdf where ${PLUGIN})/bin/script.sh ``` ### asdfのShimのバイパス 何らかの理由でasdfのShimをバイパスしたい場合や、プロジェクトのディレクトリに移動した際に自動的に環境変数を設定したい場合は、[asdf-direnv](https://github.com/asdf-community/asdf-direnv)プラグインが役に立ちます。詳細はREADMEをご確認ください。 ================================================ FILE: docs/ja-jp/more/community-projects.md ================================================ # コミュニティプロジェクト `asdf`に関連するコミュニティプロジェクトをいくつか紹介します: - [asdf-community](https://github.com/asdf-community): asdfプラグインの長期的なメンテナンスを目的とした コミュニティ主導の共同プロジェクトです。 - [asdf dev container](https://github.com/iloveitaly/asdf-devcontainer): GitHub Codespacesでasdfによるツール管理をサポートする、 [GitHub Dev Container](https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/introduction-to-dev-containers) です。 ::: warning 備考 asdfコアチームは、これらのプロジェクトやコードを所有していません。 asdfコアでは、ここに掲載されているものに関連する品質やセキュリティについては責任を負いません。 ::: ================================================ FILE: docs/ja-jp/more/faq.md ================================================ # FAQ ここでは、`asdf`に関するよくある質問を紹介します。 ## WSL1をサポートしていますか? WSL1 ([Windows Subsystem for Linux](https://ja.wikipedia.org/wiki/Windows_Subsystem_for_Linux) 1)は公式にはサポートしていません。`asdf`は正常に動作しない可能性があります。WSL1を公式にサポートする予定はありません。 ## WSL2をサポートしていますか? WSL2 ([Windows Subsystem for Linux](https://ja.wikipedia.org/wiki/Windows_Subsystem_for_Linux#WSL2) 2)では、あなたが選択したWSLディストリビューションに基づいて、セットアップと依存関係の解決を済ませれば、動作するはずです。 重要なのは、WSL2が正常に動作するのは、カレントワークディレクトリがWindowsドライブではなくUnixドライブである場合に _限られる_ ということです。 GitHub Actionsでホストランナーのサポートが可能になれば、WSL2でテストスイートを実行する予定ですが、現時点ではそうではないようです。 ## 新しくインストールした実行ファイルが実行できないのですが? > `npm install -g yarn`を実行したにも関わらず、`yarn`が実行できません。どうなっているの? `asdf`は[Shim]()を使って実行ファイルを管理しています。プラグインによってインストールされるものは、自動的にShimが作成されますが、`asdf`が管理しているツールによって実行ファイルがインストールされた場合は、Shimを作成しなければならないということを`asdf`に通知する必要があります。上記の例では、[Yarn](https://yarnpkg.com/)のShimを作成しなければいけません。詳しくは、[`asdf reshim`コマンドのドキュメント](/ja-jp/manage/core.md#shimの再作成)をご覧ください。 ## シェルが、新しくインストールされたShimを検知してくれないのですが? `asdf reshim`コマンドを実行しても問題が解決しない場合、`asdf.sh`や`asdf.fish`のsourceが、シェルの構成ファイル(`.bash_profile`、`.zshrc`、`config.fish`など)の**一番下**にないことが原因である可能性があります。`$PATH`を設定した**後**、そしてフレームワーク(oh-my-zshなど)を使用しているのれあればそれをsourceした**後**に、sourceする必要があります。 ================================================ FILE: docs/ja-jp/more/thanks.md ================================================ # 謝辞 このページを借りて、asdfのオーサーおよびコントリビューターの皆様に感謝を申し上げます! ## クレジット 私 ([@HashNuke](https://github.com/HashNuke))、高熱、風邪、咳。 コピーライト 2014年から今まで ([MIT License](https://github.com/asdf-vm/asdf/blob/master/LICENSE)) ## メンテナー - [@HashNuke](https://github.com/HashNuke) - [@danhper](https://github.com/danhper) - [@Stratus3D](https://github.com/Stratus3D) - [@vic](https://github.com/vic) - [@jthegedus](https://github.com/jthegedus) ## コントリビューター GitHubの[コントリビューターリスト](https://github.com/asdf-vm/asdf/graphs/contributors):pray:をご覧ください。 ================================================ FILE: docs/ja-jp/plugins/create.md ================================================ # プラグインの作成 プラグインとは、 言語/ツールのバージョン管理をサポートするための実行スクリプトを含めたGitリポジトリのことです。 これらのスクリプトは、`asdf list-all `や`asdf install `などの機能をサポートするコマンドを使って、 asdfによって実行されます。 ## クイックスタート オリジナルのプラグインを作成するには、次の2つの方法があります: 1. [asdf-vm/asdf-plugin-template](https://github.com/asdf-vm/asdf-plugin-template)リポジトリを使用し、 デフォルトのスクリプトが実装されたプラグインリポジトリ(名前は`asdf-`)を [生成](https://github.com/asdf-vm/asdf-plugin-template/generate) します。 生成できたら、 そのリポジトリをクローンして`setup.bash`のスクリプトを実行し、 テンプレートを対話的に更新していきます。 2. `asdf-`という名前のリポジトリを自分で立ち上げ、 以降に記載されている必要なスクリプトを実装します。 ### プラグインスクリプトの鉄則 - スクリプト内で他の`asdf`コマンドを呼び出しては**いけません**。 - シェルのツール/コマンドへの依存関係を小さく保つようにしてください。 - 移植性のないツールやコマンドフラグの使用は避けてください。 例えば、`sort -V`などです。 asdfコアの[禁止コマンド一覧](https://github.com/asdf-vm/asdf/blob/master/test/banned_commands.bats)もご覧ください。 ## スクリプトの概要 以下は、asdfから呼び出せるスクリプトの全リストです。 | スクリプト | 説明 | | :--------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------- | | [bin/list-all](#bin-list-all) | インストール可能なすべてのバージョンをリストします。 | | [bin/download](#bin-download) | ツールの特定バージョンのソースコードまたはバイナリをダウンロードします。 | | [bin/install](#bin-install) | ツールの特定バージョンをインストールします。 | | [bin/latest-stable](#bin-latest-stable) | 指定されたツールの最新安定バージョンをリストします。 | | [bin/help.overview](#bin-help.overview) | プラグインおよびツールに関する概要説明を出力します。 | | [bin/help.deps](#bin-help.deps) | オペレーティングシステムに合わせた依存関係のリストを出力します。 | | [bin/help.config](#bin-help.config) | プラグインおよびツールの構成設定一覧を出力します。 | | [bin/help.links](#bin-help.links) | プラグインとツールに関連するリンクリストを出力します。 | | [bin/list-bin-paths](#bin-list-bin-paths) | Shimを作成するバイナリが存在するディレクトリへの相対パスの一覧を出力します。 | | [bin/exec-env](#bin-exec-env) | ツールのバイナリのShimを実行する前に環境を準備します。 | | [bin/exec-path](#bin-exec-path) | ツールの特定バージョンの実行ファイルパスを出力します。 | | [bin/uninstall](#bin-uninstall) | ツールの特定バージョンをアンインストールします。 | | [bin/list-legacy-filenames](#bin-list-legacy-filenames) | `.ruby-version`のような、レガシー構成ファイルのリストを出力します。 | | [bin/parse-legacy-file](#bin-parse-legacy-file) | レガシーバージョンファイルのカスタムパーサーです。 | | [bin/post-plugin-add](#bin-post-plugin-add) | プラグインが追加された後に実行されるフックです。 | | [bin/post-plugin-update](#bin-post-plugin-update) | プラグインが更新された後に実行されるフックです。 | | [bin/pre-plugin-remove](#bin-pre-plugin-remove) | プラグインが削除される前に実行されるフックです。 | どのコマンドがどのスクリプトを呼び出すかについては、 各スクリプトの詳細なドキュメントを参照してください。 ## 環境変数の概要 以下は、すべてのスクリプトで使用される環境変数の全リストです。 | 環境変数 | 説明 | | :----------------------- | :---------------------------------------------------------------------------------------------- | | `ASDF_INSTALL_TYPE` | `version`または`ref`です。 | | `ASDF_INSTALL_VERSION` | `ASDF_INSTALL_TYPE`に応じてフルバージョンナンバーまたはGit Refの値が入ります。 | | `ASDF_INSTALL_PATH` | ツールがインストール _されている_ 場所、またはインストール _されるべき_ 場所へのパスです。 | | `ASDF_CONCURRENCY` | ソースコードのコンパイル時に使用するコア数です。`make -j`のようなフラグを設定する際に便利です。 | | `ASDF_DOWNLOAD_PATH` | `bin/download`によってソースコードまたはバイナリがダウンロードされる場所へのパスです。 | | `ASDF_PLUGIN_PATH` | プラグインがインストールされている場所へのパスです。 | | `ASDF_PLUGIN_SOURCE_URL` | プラグインソースのURLです。 | | `ASDF_PLUGIN_PREV_REF` | プラグインの以前の`git-ref`です。 | | `ASDF_PLUGIN_POST_REF` | 更新後のプラグインの`git-ref`です。 | | `ASDF_CMD_FILE` | ソースとなるファイルのフルパスに解決されます。 | ::: tip 備考 **すべてのスクリプトですべての環境変数が使用できるわけではありません。** 以下の各スクリプトのドキュメントで、そのスクリプトで利用可能な環境変数を確認してください。 ::: ## 必須スクリプト ### `bin/list-all` **説明** インストール可能なすべてのバージョンをリストします。 **出力フォーマット** **スペース区切り**のバージョンリストの文字列を出力する必要があります。例えば次のとおりです: ```txt 1.0.1 1.0.2 1.3.0 1.4 ``` 最新バージョンが末尾にくる必要があります。 asdfコアは各バージョンを1行ずつ表示するため、 いくつかのバージョンは画面外にはみ出る場合があります。 **並べ替え** ウェブサイト上のリリースページからバージョンを取得する場合、 提供されている順序は正しいリリース順となっていることが多いため、 そのままの順序を使用することを推奨します。 逆順にしたければ、`tsc`をパイプで通すだけで十分です。 どうしても並べ替えが避けられない場合、`sort -V`は移植性が無いため、次のいずれかの方法を使用することを推奨します: - [Git sort capabilityを使用する](https://github.com/asdf-vm/asdf-plugin-template/blob/main/template/lib/utils.bash) (Git `v2.18.0`以上が必要です) - [カスタムソートメソッドを自分で書く](https://github.com/vic/asdf-idris/blob/master/bin/list-all#L6) (`sed`、`sort`、および`awk`が必要です) **スクリプトで使用できる環境変数** このスクリプトに環境変数は提供されません。 **このスクリプトを呼び出すコマンド** - `asdf list all [version]` - `asdf list all nodejs`: このスクリプトで返されるすべてのバージョンを、 1行ずつリストします。 - `asdf list all nodejs 18`: このスクリプトで返されるすべてのバージョンから、 `18`で始まるバージョンのみフィルタし、1行ずつリストします。 **asdfからの呼び出しシグネチャ** 引数はありません。 ```bash "${plugin_path}/bin/list-all" ``` --- ### `bin/download` **説明** ツールの特定バージョンのソースコードまたはバイナリを、指定された場所にダウンロードします。 **実装内容** - スクリプトは、`ASDF_DOWNLOAD_PATH`で指定されたディレクトリに、ソースコードまたはバイナリをダウンロードする必要があります。 - 解凍されたソースコードまたはバイナリのみを、`ASDF_DOWNLOAD_PATH`ディレクトリに配置する必要があります。 - 失敗した場合、`ASDF_DOWNLOAD_PATH`ディレクトリ内に何もファイルを配置しないようにしてください。 - 成功した場合、終了コードは`0`としてください。 - 失敗した場合、終了コードは非ゼロとしてください。 **レガシープラグイン** このスクリプトはすべてのプラグインで _必須_ とされていますが、このスクリプトが導入される以前の"レガシー"プラグインでは、 _オプション_ となっていました。 このスクリプトが存在しない場合、asdfは`bin/install`スクリプトがあると想定して、バージョンのダウンロード、**かつ**、インストールが実行されます。 レガシープラグインのサポートは最終的に削除される予定のため、今後作成するすべてのプラグインでこのスクリプトを含めるようにしてください。 **スクリプトで使用できる環境変数** - `ASDF_INSTALL_TYPE`: `version`または`ref`です。。 - `ASDF_INSTALL_VERSION`: - `ASDF_INSTALL_TYPE=version`の場合、バージョンのフルナンバーです。 - `ASDF_INSTALL_TYPE=ref`の場合、Gitのref (tag/commit/branch)です。 - `ASDF_INSTALL_PATH`: ツールがインストール _されている_ 場所、またはインストール _されるべき_ 場所へのパスです。 - `ASDF_DOWNLOAD_PATH`: ソースコードまたはバイナリのダウンロード先のパスです。 **このスクリプトを呼び出すコマンド** - `asdf install [version]` - `asdf install latest[:version]` - `asdf install nodejs 18.0.0`: Node.jsのバージョン`18.0.0`のソースコードまたはバイナリをダウンロードし、`ASDF_DOWNLOAD_PATH`ディレクトリに配置します。 そして`bin/install`スクリプトを実行します。 **asdfからの呼び出しシグネチャ** 引数はありません。 ```bash "${plugin_path}"/bin/download ``` --- ### `bin/install` **説明** ツールの特定バージョンを指定された場所にインストールします。 **実装内容** - スクリプトは、指定されたバージョンを`ASDF_INSTALL_PATH`のパスのディレクトリにインストールする必要があります。 - Shimはデフォルトで、`$ASDF_INSTALL_PATH/bin`内にあるファイルに対して作成されます。 この動作は、オプションの[bin/list-bin-paths](#binlist-bin-paths)スクリプトでカスタマイズできます。 - 成功した場合、終了コードは`0`としてください。 - 失敗した場合、終了コードは非ゼロとしてください。 - TOCTOU (Time-of-Check-to-Time-of-Use)の問題を避けるために、ツールのビルドとインストールが成功したとみなされた場合にのみ、`ASDF_INSTALL_PATH`にファイルを配置するようなスクリプトとしてください。 **レガシープラグイン** `bin/download`スクリプトが存在しない場合、このスクリプトでは、指定されたバージョンをダウンロード、**かつ**、インストールをする必要があります。 `0.7._`以前と`0.8._`以降のasdfコアの互換性を保つために、`ASDF_DOWNLOAD_PATH`環境変数が設定されているかを確認してください。 設定されている場合は、`bin/download`スクリプトがすでにバージョンをダウンロードしていると想定し、設定されていない場合は、`bin/install`でソースコードをダウンロードするようにしてください。 **スクリプトで使用できる環境変数** - `ASDF_INSTALL_TYPE`: `version`または`ref`です。 - `ASDF_INSTALL_VERSION`: - `ASDF_INSTALL_TYPE=version`の場合、バージョンのフルナンバーです。 - `ASDF_INSTALL_TYPE=ref`の場合、Gitのref (tag/commit/branch)です。 - `ASDF_INSTALL_PATH`: ツールがインストール _されている_ 場所、またはインストール _されるべき_ 場所へのパスです。 - `ASDF_CONCURRENCY`: ソースコードのコンパイル時に使用するコア数です。`make -j`のようなフラグを設定する際に便利です。 - `ASDF_DOWNLOAD_PATH`: ソースコードまたはバイナリのダウンロード先のパスです。 **このスクリプトを呼び出すコマンド** - `asdf install` - `asdf install ` - `asdf install [version]` - `asdf install latest[:version]` - `asdf install nodejs 18.0.0`: `ASDF_INSTALL_PATH`ディレクトリに、 Node.jsのバージョン`18.0.0`をインストールします。 **asdfからの呼び出しシグネチャ** 引数はありません。 ```bash "${plugin_path}"/bin/install ``` ## オプションスクリプト ### `bin/latest-stable` **説明** ツールの最新安定バージョンを判定します。このスクリプトが存在しない場合、asdfコアは`bin/list-all`の出力を`tail`した結果をもとに判定しますが、ツールによってはこれが望ましくないことがあります。 **実装内容** - スクリプトは、ツールの最新安定バージョンを標準出力する必要があります。 - 非安定版やリリース候補版は除外されるべきです。 - フィルタクエリは、スクリプトの第1引数で提供されます。このクエリは、バージョン番号やツールプロバイダによる出力をフィルタするために使用されるべきです。 - 例えば、[rubyプラグイン](https://github.com/asdf-vm/asdf-ruby)での`asdf list all ruby`の出力は、`jruby`や`rbx`、`truffleruby`などの多くのプロバイダのRubyバージョンをリストアップします。ユーザが提供したフィルタは、セマンティックバージョンやプロバイダをフィルタするために、プラグインで使用できます。 ``` > asdf latest ruby 3.2.2 > asdf latest ruby 2 2.7.8 > asdf latest ruby truffleruby truffleruby+graalvm-22.3.1 ``` - 成功した場合、終了コードは`0`としてください。 - 失敗した場合、終了コードは非ゼロとしてください。 **スクリプトで使用できる環境変数** - `ASDF_INSTALL_TYPE`: `version`または`ref`です。 - `ASDF_INSTALL_VERSION`: - `ASDF_INSTALL_TYPE=version`の場合、バージョンのフルナンバーです。 - `ASDF_INSTALL_TYPE=ref`の場合、Gitのref (tag/commit/branch)です。 - `ASDF_INSTALL_PATH`: ツールがインストール _されている_ 場所、またはインストール _されるべき_ 場所へのパスです。 **このスクリプトを呼び出すコマンド** - `asdf set latest`: ツールのバージョンをそのツールの最新安定バージョンに設定します。 - `asdf install latest`: ツールの最新安定バージョンをインストールします。 - `asdf latest []`: オプションのフィルタに基づいて、ツールの最新バージョンを出力します。 - `asdf latest --all`: asdfによって管理されているすべてのツールの最新バージョンと、それらがインストールされているかどうかを出力します。 **asdfからの呼び出しシグネチャ** このスクリプトは、フィルタクエリという1つの引数を受け取ります。 ```bash "${plugin_path}"/bin/latest-stable "$query" ``` --- ### `bin/help.overview` **説明** プラグインおよび管理されているツールに関する概要説明を出力します。 **実装内容** - このスクリプトは、プラグインのヘルプを表示するために必要です。 - ヘッダはasdfコア側で表示するため、スクリプト内では表示しないでください。 - 自由な形式のテキストで出力して構いませんが、短い1段落程度の説明が理想です。 - コアとなるasdf-vmドキュメントですでに説明されている情報は出力しないでください。 - オペレーティングシステムと、インストールされているツールのバージョンに合わせて出力を調整する必要があります(必要に応じて、`ASDF_INSTALL_VERSION`および`ASDF_INSTALL_TYPE`環境変数の値を使用してください)。 - 成功した場合、終了コードは`0`としてください。 - 失敗した場合、終了コードは非ゼロとしてください。 **スクリプトで使用できる環境変数** - `ASDF_INSTALL_TYPE`: `version`または`ref`です。 - `ASDF_INSTALL_VERSION`: - `ASDF_INSTALL_TYPE=version`の場合、バージョンのフルナンバーです。 - `ASDF_INSTALL_TYPE=ref`の場合、Gitのref (tag/commit/branch)です。 - `ASDF_INSTALL_PATH`: ツールがインストール _されている_ 場所、またはインストール _されるべき_ 場所へのパスです。 **このスクリプトを呼び出すコマンド** - `asdf help []`: プラグインおよびツールのドキュメントを出力します。 **asdfからの呼び出しシグネチャ** ```bash "${plugin_path}"/bin/help.overview ``` --- ### `bin/help.deps` **説明** オペレーティングシステムに合わせた依存関係のリストを出力します。依存関係を1行ごとに出力します。 ```bash git curl sed ``` **実装内容** - このスクリプトの出力を考慮するために、`bin/help.overview`を用意する必要があります。 - オペレーティングシステムと、インストールされているツールのバージョンに合わせて出力を調整する必要があります(必要に応じて、`ASDF_INSTALL_VERSION`および`ASDF_INSTALL_TYPE`環境変数の値を使用してください)。 - 成功した場合、終了コードは`0`としてください。 - 失敗した場合、終了コードは非ゼロとしてください。 **スクリプトで使用できる環境変数** - `ASDF_INSTALL_TYPE`: `version`または`ref`です。 - `ASDF_INSTALL_VERSION`: - `ASDF_INSTALL_TYPE=version`の場合、バージョンのフルナンバーです。 - `ASDF_INSTALL_TYPE=ref`の場合、Gitのref (tag/commit/branch)です。 - `ASDF_INSTALL_PATH`: ツールがインストール _されている_ 場所、またはインストール _されるべき_ 場所へのパスです。 **このスクリプトを呼び出すコマンド** - `asdf help []`: プラグインおよびツールのドキュメントを出力します。 **asdfからの呼び出しシグネチャ** ```bash "${plugin_path}"/bin/help.deps ``` --- ### `bin/help.config` **説明** プラグインおよびツールで設定必須または任意設定可能な構成設定一覧を出力します。例えば、ツールのインストール・コンパイルに必要な環境変数やその他フラグについて説明します。 **実装内容** - このスクリプトの出力を考慮するために、`bin/help.overview`を用意する必要があります。 - 自由な形式のテキストで出力できます。 - オペレーティングシステムと、インストールされているツールのバージョンに合わせて出力を調整する必要があります(必要に応じて、`ASDF_INSTALL_VERSION`および`ASDF_INSTALL_TYPE`環境変数の値を使用してください)。 - 成功した場合、終了コードは`0`としてください。 - 失敗した場合、終了コードは非ゼロとしてください。 **スクリプトで使用できる環境変数** - `ASDF_INSTALL_TYPE`: `version`または`ref`です。 - `ASDF_INSTALL_VERSION`: - `ASDF_INSTALL_TYPE=version`の場合、バージョンのフルナンバーです。 - `ASDF_INSTALL_TYPE=ref`の場合、Gitのref (tag/commit/branch)です。 - `ASDF_INSTALL_PATH`: ツールがインストール _されている_ 場所、またはインストール _されるべき_ 場所へのパスです。 **このスクリプトを呼び出すコマンド** - `asdf help []`: プラグインおよびツールのドキュメントを出力します。 **asdfからの呼び出しシグネチャ** ```bash "${plugin_path}"/bin/help.config ``` --- ### `bin/help.links` **説明** プラグインとツールに関連するリンクリストを出力します。リンクを1行ごとに出力します。 ```bash Git Repository: https://github.com/vlang/v Documentation: https://vlang.io ``` **実装内容** - このスクリプトの出力を考慮するために、`bin/help.overview`を用意する必要があります。 - リンクを1行ごとに出力してください。 - 形式は以下のいずれかである必要があります: - `: <link>` - または`<link>`のみ - オペレーティングシステムと、インストールされているツールのバージョンに合わせて出力を調整する必要があります(必要に応じて、`ASDF_INSTALL_VERSION`および`ASDF_INSTALL_TYPE`環境変数の値を使用してください)。 - 成功した場合、終了コードは`0`としてください。 - 失敗した場合、終了コードは非ゼロとしてください。 **スクリプトで使用できる環境変数** - `ASDF_INSTALL_TYPE`: `version`または`ref`です。 - `ASDF_INSTALL_VERSION`: - `ASDF_INSTALL_TYPE=version`の場合、バージョンのフルナンバーです。 - `ASDF_INSTALL_TYPE=ref`の場合、Gitのref (tag/commit/branch)です。 - `ASDF_INSTALL_PATH`: ツールがインストール _されている_ 場所、またはインストール _されるべき_ 場所へのパスです。 **このスクリプトを呼び出すコマンド** - `asdf help <name> [<version>]`: プラグインおよびツールのドキュメントを出力します。 **asdfからの呼び出しシグネチャ** ```bash "${plugin_path}"/bin/help.links ``` --- ### `bin/list-bin-paths` **説明** ツールの特定バージョンにおける、実行ファイルが含まれるディレクトリの一覧を出力します。 **実装内容** - このスクリプトが存在しない場合、asdfは`"${ASDF_INSTALL_PATH}"/bin`ディレクトリ内にあるバイナリを探し、そのバイナリ向けのShimを作成します。 - 実行ファイルが含まれるディレクトリのパスをスペース区切りで出力してください。 - パスは`ASDF_INSTALL_PATH`からの相対パスである必要があります。例えば、次のような出力となります: ```bash bin tools veggies ``` 以上の場合、下記ディレクトリ内のファイルへのShimを作成するよう、asdfへ指示されます: - `"${ASDF_INSTALL_PATH}"/bin` - `"${ASDF_INSTALL_PATH}"/tools` - `"${ASDF_INSTALL_PATH}"/veggies` **スクリプトで使用できる環境変数** - `ASDF_INSTALL_TYPE`: `version`または`ref`です。 - `ASDF_INSTALL_VERSION`: - `ASDF_INSTALL_TYPE=version`の場合、バージョンのフルナンバーです。 - `ASDF_INSTALL_TYPE=ref`の場合、Gitのref (tag/commit/branch)です。 - `ASDF_INSTALL_PATH`: ツールがインストール _されている_ 場所、またはインストール _されるべき_ 場所へのパスです。 **このスクリプトを呼び出すコマンド** - `asdf install <tool> [version]`: バイナリへのShimを初期作成します。 - `asdf reshim <tool> <version>`: バイナリへのShimを再作成します。 **asdfからの呼び出しシグネチャ** ```bash "${plugin_path}/bin/list-bin-paths" ``` --- ### `bin/exec-env` **説明** ツールのバイナリのShimを実行する前に環境を準備します。 **スクリプトで使用できる環境変数** - `ASDF_INSTALL_TYPE`: `version`または`ref`です。 - `ASDF_INSTALL_VERSION`: - `ASDF_INSTALL_TYPE=version`の場合、バージョンのフルナンバーです。 - `ASDF_INSTALL_TYPE=ref`の場合、Gitのref (tag/commit/branch)です。 - `ASDF_INSTALL_PATH`: ツールがインストール _されている_ 場所、またはインストール _されるべき_ 場所へのパスです。 **このスクリプトを呼び出すコマンド** - `asdf which <command>`: 実行ファイルのパスを表示します。 - `asdf exec <command> [args...]`: 現在のバージョンでShimコマンドを実行します。 - `asdf env <command> [util]`: Shimコマンドの実行時に使用される環境において、util(デフォルト: `env`)を実行します。 **asdfからの呼び出しシグネチャ** ```bash "${plugin_path}/bin/exec-env" ``` --- ### `bin/exec-path` ツールの特定バージョンの実行ファイルパスを取得します。 実行ファイルへの相対パスを文字列で出力する必要があります。 これにより、プラグインはShimで指定された実行ファイルパスを条件付きで上書きして返すか、 そうでなければ、Shimで指定されたデフォルトのパスを返すことができます。 **説明** ツールの特定バージョンの実行ファイルパスを取得します。 **実装内容** - 実行ファイルへの相対パスを文字列で出力する必要があります。 - Shimで指定された実行ファイルパスを条件付きで上書きして返すか、そうでなければ、Shimで指定されたデフォルトのパスを返してください。 ```shell Usage: plugin/bin/exec-path <install-path> <command> <executable-path> Example Call: ~/.asdf/plugins/foo/bin/exec-path "~/.asdf/installs/foo/1.0" "foo" "bin/foo" Output: bin/foox ``` **スクリプトで使用できる環境変数** - `ASDF_INSTALL_TYPE`: `version`または`ref`です。 - `ASDF_INSTALL_VERSION`: - `ASDF_INSTALL_TYPE=version`の場合、バージョンのフルナンバーです。 - `ASDF_INSTALL_TYPE=ref`の場合、Gitのref (tag/commit/branch)です。 - `ASDF_INSTALL_PATH`: ツールがインストール _されている_ 場所、またはインストール _されるべき_ 場所へのパスです。 **このスクリプトを呼び出すコマンド** - `asdf which <command>`: 実行ファイルのパスを表示します。 - `asdf exec <command> [args...]`: 現在のバージョンでShimコマンドを実行します。 - `asdf env <command> [util]`: Shimコマンドの実行時に使用される環境において、util(デフォルト: `env`)を実行します。 **asdfからの呼び出しシグネチャ** ```bash "${plugin_path}/bin/exec-path" "$install_path" "$cmd" "$relative_path" ``` --- ### `bin/uninstall` **説明** ツールの特定バージョンをアンインストールします。 **出力フォーマット** ユーザへの出力は、`stdout`または`stderr`へ適切に送信してください。後続のコア実行によってこれらの出力が読み取られることはありません。 **スクリプトで使用できる環境変数** このスクリプトに環境変数は提供されません。 **このスクリプトを呼び出すコマンド** - `asdf list all <name> <version>` - `asdf uninstall nodejs 18.15.0`: nodejsのバージョン`18.15.0`をアンインストールし、`npm i -g`でグローバルにインストールしたものを含むすべてのShimを削除します。 **asdfからの呼び出しシグネチャ** 引数はありません。 ```bash "${plugin_path}/bin/uninstall" ``` --- ### `bin/list-legacy-filenames` **説明** ツールのバージョンを決定するために使用されるレガシー構成ファイルのリストを出力します。 **実装内容** - スペース区切りのファイル名リストを出力してください。 ```bash .ruby-version .rvmrc ``` - この内容は、`"${HOME}"/.asdfrc`内の`legacy_version_file`オプションを有効にしたユーザにのみ適用されます。 **スクリプトで使用できる環境変数** - `ASDF_INSTALL_TYPE`: `version`または`ref`です。 - `ASDF_INSTALL_VERSION`: - `ASDF_INSTALL_TYPE=version`の場合、バージョンのフルナンバーです。 - `ASDF_INSTALL_TYPE=ref`の場合、Gitのref (tag/commit/branch)です。 - `ASDF_INSTALL_PATH`: ツールがインストール _されている_ 場所、またはインストール _されるべき_ 場所へのパスです。 **このスクリプトを呼び出すコマンド** ツールのバージョンを読み込むすべてのコマンドから呼び出されます。 **asdfからの呼び出しシグネチャ** 引数はありません。 ```bash "${plugin_path}/bin/list-legacy-filenames" ``` --- ### `bin/parse-legacy-file` **説明** asdfによって発見されたレガシーファイルをパースして、ツールのバージョンを決定します。JavaScriptの`package.json`や、Go言語の`go.mod`のようなファイルから、バージョン番号を抽出するのに役立ちます。 **実装内容** - このスクリプトが存在しない場合、asdfは単純にレガシーファイルを`cat`してバージョンを決定します。 - **決定論的**で、常に正確で同じバージョンを返す必要があります: - 同じレガシーファイルを解析したら、同じバージョンを返すようにしてください。 - マシンに何がインストールされているか、また、レガシーバージョンが有効で完全かどうかは関係ありません。一部のレガシーファイルのフォーマットは適切でないときもあります。 - 下記のように、バージョン番号を1行で出力してください: ```bash 1.2.3 ``` **スクリプトで使用できる環境変数** このスクリプトが呼び出される前に、環境変数が設定されることはありません。 **このスクリプトを呼び出すコマンド** ツールのバージョンを読み込むすべてのコマンドから呼び出されます。 **asdfからの呼び出しシグネチャ** このスクリプトは、レガシーファイルの内容を読み込むために、レガシーファイルのパスという1つの引数を受け取ります。 ```bash "${plugin_path}/bin/parse-legacy-file" "$file_path" ``` --- ### `bin/post-plugin-add` **説明** このスクリプトは、asdfの`asdf plugin add <tool>`コマンドで、プラグインが _追加_ された **後に** 呼び出されます。 関連するコマンドフックについても参照してください: - `pre_asdf_plugin_add` - `pre_asdf_plugin_add_${plugin_name}` - `post_asdf_plugin_add` - `post_asdf_plugin_add_${plugin_name}` **スクリプトで使用できる環境変数** - `ASDF_PLUGIN_PATH`: プラグインがインストールされている場所へのパスです。 - `ASDF_PLUGIN_SOURCE_URL`: プラグインソースのURLです。ローカルディレクトリパスを指定することもできます。 **asdfからの呼び出しシグネチャ** 引数はありません。 ```bash "${plugin_path}/bin/post-plugin-add" ``` --- ### `bin/post-plugin-update` **説明** このスクリプトは、asdfの`asdf plugin update <tool> [<git-ref>]`コマンドで、 _更新_ されたプラグインがダウンロードされた **後に** 呼び出されます。 関連するコマンドフックについても参照してください: - `pre_asdf_plugin_updated` - `pre_asdf_plugin_updated_${plugin_name}` - `post_asdf_plugin_updated` - `post_asdf_plugin_updated_${plugin_name}` **スクリプトで使用できる環境変数** - `ASDF_PLUGIN_PATH`: プラグインがインストールされている場所へのパスです。 - `ASDF_PLUGIN_PREV_REF`: プラグインの以前のgit-refです。 - `ASDF_PLUGIN_POST_REF`: 更新後のプラグインのgit-refです。 **asdfからの呼び出しシグネチャ** 引数はありません。 ```bash "${plugin_path}/bin/post-plugin-update" ``` --- ### `bin/pre-plugin-remove` **説明** このスクリプトは、asdfの`asdf plugin remove <tool>`コマンドで、プラグインが _削除_ される **前に** 呼び出されます。 関連するコマンドフックについても参照してください: - `pre_asdf_plugin_remove` - `pre_asdf_plugin_remove_${plugin_name}` - `post_asdf_plugin_remove` - `post_asdf_plugin_remove_${plugin_name}` **スクリプトで使用できる環境変数** - `ASDF_PLUGIN_PATH`: プラグインがインストールされている場所へのパスです。 **asdfからの呼び出しシグネチャ** 引数はありません。 ```bash "${plugin_path}/bin/pre-plugin-remove" ``` <!-- TODO: document command hooks --> <!-- ## Command Hooks --> ## asdf CLIの拡張コマンド <Badge type="danger" text="高度" vertical="middle" /> プラグイン名をサブコマンドとして使用し、 asdfコマンドラインインターフェースを通して呼び出すことのできる`lib/commands/command*.bash`スクリプトまたは実行ファイルを用意することで、 新しいasdfコマンドを定義することができます。 例えば、`foo`というプラグインがあるとすると: ```shell foo/ lib/commands/ command.bash command-bat.bash command-bat-man.bash command-help.bash ``` ユーザは下記コマンドが実行できるようになります: ```shell $ 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` $ asdf foo bat man # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-bat-man.bash` $ asdf foo bat baz # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-bat.bash baz` ``` プラグイン開発者はこの機能を使って、ツールに関連するユーティリティを提供したり、 asdf自体のコマンド拡張プラグインを作成したりすることができます。 実行可能ビット(executable bit)が付与されている場合、 asdfの実行に代わって、当該スクリプトが実行されます。 実行可能ビット(executable bit)が付与されていない場合、asdfは当該スクリプトをBashスクリプトとしてsourceします。 `$ASDF_CMD_FILE`環境変数は、ソースとなるファイルのフルパスに解決されます。 [`haxe`](https://github.com/asdf-community/asdf-haxe)は、 この機能使ったプラグインの素晴らしい例です。 このプラグインは、`asdf haxe neko-dylibs-link`を提供しており、 Haxeの実行ファイルが実行ディレクトリから相対的に動的ライブラリを見つけようとしてしまう問題を修正します。 プラグインのREADMEには、asdf拡張コマンドに関することを必ず記載するようにしてください。 ## カスタムShimテンプレート <Badge type="danger" text="高度" vertical="middle" /> ::: warning 警告 **どうしても**必要な場合にのみ使用してください。 ::: asdfでは、カスタムShimテンプレートを使用することができます。 `foo`という実行ファイルに対して、プラグイン内に`shims/foo`ファイルが存在すれば、 asdfは標準Shimテンプレートを使用する代わりに、そのファイルをコピーします。 **この機能は賢く使う必要があります。** asdfコアチームが把握している限り、 この機能は公式プラグインである[Elixirプラグイン](https://github.com/asdf-vm/asdf-elixir)でのみ使用されています。 実行ファイルは、実行ファイルであると同時に、Elixirファイルとしても読み込まれます。 そのため、標準的なBashのShimを使用できないのです。 ## テスト asdfでは、プラグインをテストするための`plugin-test`コマンドを用意しており、下記のように使用できます: ```shell asdf plugin test <plugin_name> <plugin_url> [--asdf-tool-version <version>] [--asdf-plugin-gitref <git_ref>] [test_command...] ``` - `<plugin_name>`と`<plugin_url>`は必須です。 - オプションで`[--asdf-tool-version <version>]`を指定すると、そのバージョンのツールがインストールされます。 デフォルトは、`asdf latest <plugin-name>`です。 - オプションで`[--asdf-plugin-gitref <git_ref>]`を指定すると、 そのコミット/ブランチ/タグでプラグイン自体をチェックアウトします。 これは、プラグインのCIにおいて、プルリクエストをテストする際に便利です。デフォルトは、プラグインリポジトリのデフォルトブランチとなります。 - オプションの`[test_command...]`パラメータは、インストールしたツールが正しく動作するかを確認するために実行するコマンドです。 通常は、`<tool> --version`または`<tool> --help`となります。 例えば、NodeJSプラグインをテストするときは、次のように実行します: ```shell # asdf plugin test <plugin_name> <plugin_url> [test_command] asdf plugin test nodejs https://github.com/asdf-vm/asdf-nodejs.git node --version ``` ::: tip 備考 LinuxとmacOSの両方のCI環境でテストすることを推奨します。 ::: ### GitHub Action [asdf-vm/actions](https://github.com/asdf-vm/actions)リポジトリでは、 GitHub上でホストされているプラグインをテストするためのGitHub Actionを提供しています。 `.github/workflows/test.yamlのActionsワークフローの例は以下のとおりです: ```yaml 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" ``` ### TravisCI 構成設定 以下は、`.travis.yml`ファイルの例です。必要に応じてカスタマイズしてください: ```yaml language: c 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 os: - linux - osx ``` ::: tip 備考 他のCIを使用する場合、 プラグインの場所への相対パスを渡す必要がある場合があります: ```shell asdf plugin test <tool_name> <path> '<tool_command> --version' ``` ::: ## APIレート制限 `bin/list-all`や`bin/latest-stable`のように、コマンドが外部APIへのアクセスに依存している場合、 自動テスト中にレート制限が発生することがあります。 これを軽減するため、環境変数経由で認証トークンを提供するコードパスがあることを確認してください。 以下に例を示します: ```shell cmd="curl --silent" if [ -n "$GITHUB_API_TOKEN" ]; then cmd="$cmd -H 'Authorization: token $GITHUB_API_TOKEN'" fi cmd="$cmd $releases_path" ``` ### `GITHUB_API_TOKEN` `GITHUB_API_TOKEN`を利用する際は、 まず、 `public_repo`アクセスのみをもつ[新しいパーソナルトークン](https://github.com/settings/tokens/new)を作成してください。 次に、このトークンをCIパイプライン環境変数に追加してください。 ::: warning 警告 認証トークンをコードリポジトリで公開してはいけません。 ::: ## プラグインショートネームインデックス ::: tip ヒント 推奨されるプラグインのインストール方法は、URLをもとに直接インストールする方法です: ```shell # asdf plugin add <name> <git_url> asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs ``` ::: asdfの各種コマンドで`git_url`が指定されなかった場合、 asdfは正確な`git_url`を決定するために、 [ショートネームインデックスリポジトリ](https://github.com/asdf-vm/asdf-plugins)を使用します。 このリポジトリの指示に従うことで、 あなたが作成したプラグインを、 [ショートネームインデックス](https://github.com/asdf-vm/asdf-plugins)に追加することができます。 ================================================ FILE: docs/ko-kr/contribute/core.md ================================================ # asdf `asdf` 코어 기여 가이드. ## 초기 설정 Github의 `asdf`를 fork하거나 clone하세요: ```shell # clone your fork git clone https://github.com/<GITHUB_USER>/asdf.git # or clone asdf git clone https://github.com/asdf-vm/asdf.git ``` 코어 개발을 위한 툴들은 이 리포지토리의 `.tool-versions`에 있습니다. 만약 `asdf`가 직접 관리하기를 바라신다면, 다음 플러그인들을 설치하세요: ```shell asdf plugin add bats https://github.com/timgluz/asdf-bats.git asdf plugin add shellcheck https://github.com/luizm/asdf-shellcheck.git asdf plugin add shfmt https://github.com/luizm/asdf-shfmt.git ``` `asdf`를 개발하기 위한 버전들을 설치하세요: ```shell asdf install ``` 로컬 머신에서 개발 도구를 손상시킬 수 있는 기능을 개발 중일 경우 `asdf`를 사용하지 않는 것이 _좋을 수_ 있습니다. 개발 도구들의 원본 목록입니다: - [bats-core](https://github.com/bats-core/bats-core): Bash 또는 POSIX 준수 스크립트를 단위 테스트하기 위한 Bash 자동화 테스트 시스템. - [shellcheck](https://github.com/koalaman/shellcheck): 셸 스크립트 정적 분석 도구. - [shfmt](https://github.com/mvdan/sh): Bash를 지원하는 셸 parser, formatter, interpreter; shfmt 포함 ## 개발 만약 설치된 `asdf`에 영향 없이 변화들을 테스트해보시고 싶으시다면, `$ASDF_DIR` 변수를 리포지토리를 clone한 경로에 지정하시고, 그 다음 임시로 `bin`와 `shims` 디렉토리들을 경로 앞에 추가하세요. 원격 리포지토리에 커밋 혹은 push하기 전에, 당신의 코드를 format, lint, 그리고 locally test하세요. 다음 스크립트/명령어들을 사용하세요: ```shell # Lint ./scripts/lint.bash --check # Fix & Format ./scripts/lint.bash --fix # Test: all tests ./scripts/test.bash # Test: for specific command bats test/list_commands.bash ``` ::: tip **테스트 추가!** - 새로운 기능들과 버그 수정들의 리뷰 속도 향상을 위해 테스트들은 **필수적**입니다. 풀 리퀘스트 요청을 만드시기 전에 새로운 코드 경로들을 추가해주세요. [bats-core 문서](https://bats-core.readthedocs.io/en/stable/index.html) 참조 ::: ### Gitignore 다음은 `asdf-vm/asdf` 리포지토리에 `.gitignore` 파일입니다. 우리는 프로젝트에 관련된 특정한 파일들을 무시합니다. 운영체제, 툴, workflows에 관련된 파일들은 글로벌 `.gitignore` 설정에서 무시되어야합니다, [더 보기](http://stratus3d.com/blog/2018/06/03/stop-excluding-editor-temp-files-in-gitignore/). @/../.gitignore ### `.git-blame-ignore-revs` `asdf`는 `.git-blame-ignore-revs` 사용해 blame 실행에서 잡음을 줄입니다. 더 많은 정보 [git blame 문서](https://git-scm.com/docs/git-blame). 다음과 같이 `git blame`과 `.git-blame-ignore-revs`을 사용: ```sh git blame --ignore-revs-file .git-blame-ignore-revs ./test/install_command.bats ``` 선택적으로, 수동적으로 파일을 제공하는 대신 모든 `blame`에서 해당 파일을 사용하도록 설정: ```sh git config blame.ignoreRevsFile .git-blame-ignore-revs ``` IDE들에서 이 파일을 사용하도록 설정할 수 있습니다. 예를 들어, VSCode를 사용하실 경우 ([GitLens](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens)와 함께), 다음을 `.vscode/settings.json`에 추가하세요: ```json { "gitlens.advanced.blame.customArguments": [ "--ignore-revs-file", ".git-blame-ignore-revs" ] } ``` ## Bats 테스팅 로컬 테스트 실행: ```shell ./scripts/test.bash ``` 테스트 작성 전 **반드시 읽기**: - `test/`에 존재하는 테스트들 - [bats-core 문서](https://bats-core.readthedocs.io/en/stable/index.html) - `scripts/test.bash`에 존재하는 Bats 설정들 ### Bats 팁 Bats 디버깅은 때에 따라 어려울 수 있습니다. 디버깅 단순화를 위해, `-t` flag로 TAP output을 사용하여 테스트 실행 중 결과물 출력을 위한 특별한 파일 디스크립터 `>&3`를 사용할 수 있습니다. 예시: ```shell # test/some_tests.bats printf "%s\n" "Will not be printed during bats test/some_tests.bats" printf "%s\n" "Will be printed during bats -t test/some_tests.bats" >&3 ``` bats-core의 더 자세한 문서 [Printing to the Terminal](https://bats-core.readthedocs.io/en/stable/writing-tests.html#printing-to-the-terminal). ## 풀 리퀘스트, 릴리스 & 관습적 커밋 `asdf`는 자동화 배포 툴 [Release Please](https://github.com/googleapis/release-please)를 사용하여 자동으로 [SemVer](https://semver.org/) 버전을 올리고 [변동사항](https://github.com/asdf-vm/asdf/blob/master/CHANGELOG.md)을 작성합니다. 이 정보들은 지난 배포들로부터 커밋 history를 읽음으로써 결정됩니다. [유의적 커밋 메세지](https://www.conventionalcommits.org/)는 기본 브랜치의 커밋 메세지 형식이 되는 풀 리퀘스트 제목의 형식을 정의합니다. 이것은 GitHub Action에서 강제됩니다 [`amannn/action-semantic-pull-request`](https://github.com/amannn/action-semantic-pull-request). 관습적인 커밋 다음 형식을 따릅니다: ``` <type>[optional scope][optional !]: <description> <!-- examples --> fix: some fix feat: a new feature docs: some documentation update docs(website): some change for the website feat!: feature with breaking change ``` `<types>`의 모든 목록: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`. - `!`: 주요한(breaking) 변화들을 나타냅니다 - `fix`: 새로운 SemVer `patch`을 만듭니다 - `feat`: 새로운 SemVer `minor`을 만듭니다 - `<type>!`: 새로운 SemVer `major`을 만듭니다 풀 리퀘스트 제목은 반드시 이 형식을 따라야 합니다. ::: tip 풀 리퀘스트 제목을 관습적 커밋 메세지 형식을 사용하세요. ::: ## Docker 이미지 [asdf-alpine](https://github.com/vic/asdf-alpine)와 [asdf-ubuntu](https://github.com/vic/asdf-ubuntu) 프로젝트들은 asdf 툴들의 Dockerized 이미지들을 제공하기 위해 진행되고있습니다. 개발 서버의 베이스 혹은 프로덕션 앱들을 위해 docker 이미지들을 사용할 수 있습니다. ================================================ FILE: docs/ko-kr/contribute/documentation.md ================================================ # 문서 & 사이트 문서 & 사이트 기여 가이드. ## 초기 세팅 Github의 `asdf` fork 그리고/혹은 기본 브랜치 Git clone: ```shell # clone your fork git clone https://github.com/<GITHUB_USER>/asdf.git # or clone asdf git clone https://github.com/asdf-vm/asdf.git ``` 문서 사이트 개발을 위한 도구들은 `asdf`의 `docs/.tool-versions`에서 관리되고 있습니다. 플러그인들을 추가하기: ```shell asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs ``` 툴 버전들을 설치하기: ```shell asdf install ``` - [Node.js](https://nodejs.org): Chrome의 V8 JavaScript 엔진을 기반으로 구축된 JavaScript 런타임. `docs/package.json`로부터 Node.js dependencies 설치하기: ```shell npm install ``` ## 개발 [VitePress (v2)](https://vitepress.dev/)는 우리가 asdf 문서 사이트를 빌드하기 위해 사용하는 정적 사이트 생성기(SSG)입니다. 이는 사용자가 JavaScript를 사용중이지 않을때도 HTML 폴백을 지원하고, [Docsify.js](https://docsify.js.org/)와 결과적으로 VuePress를 대체하기 위해 선택되었습니다. 이는 VuePress로부터 대체된 Docsify & VitePress가 아니면 불가능했을 것입니다. 이것을 제외하면, 최소한의 설정과 함께 마크다운 작성에 집중하는 feature-set은 대부분 비슷합니다. `package.json`은 개발에 필요한 스크립트들을 포함합니다: @[code json{3-5}](../../package.json) 로컬 개발 서버 시작하기: ```shell npm run dev ``` 커밋 전 코드 형식 맞추기: ```shell npm run format ``` ## 풀 리퀘스트, 릴리스 & 관습적 커밋 `asdf`는 PR 제목들의 관습적인 커밋들에 의존하는 자동화된 배포 pipeline을 사용하고 있습니다. 더 자세한 문서는 [코어 기여 가이드](./core.md)에서 찾을 수 있습니다. 문서 업데이트를 위한 PR을 만드실때는, PR `docs: <description>` 형식인 관습적인 커밋 타입 `docs` 제목을 만들어주세요. ## Vitepress 사이트의 설정은 설정을 대표하는 JS 오브젝트의 TypeScript 파일들로 구성되어 있습니다. 그 파일들은 다음과 같습니다: - `docs/.vitepress/config.js`: 사이트를 위한 root 설정 파일. [VitePress 문서](https://vitepress.dev/reference/site-config) 참조. root 설정 단순화를 위해, _navbar_ 와 _sidebar_ 를 대표하는 더 큰 JS 객체가 추출되었고 로케일로 구분되었습니다. 다음을 참조하세요: - `docs/.vitepress/navbars.js` - `docs/.vitepress/sidebars.js` [기본 테마 참고자료](https://vitepress.dev/reference/default-theme-config)에서 위 설정들의 공식 문서를 보실 수 있습니다. ## I18n VitePress는 국제화를 공식적으로 지원합니다. root 설정 `docs/.vitepress/config.js`는 선택 dropdown에서의 지원되는 로케일들의 URL, 제목과 navbar/sidebar의 설정 레퍼런스들을 정의합니다. navbar/sidebar 설정들은 앞서 언급한 로케일 별로 나누어지고 내보내기된 설정파일들에 의해 결정됩니다. 각 로케일을 위한 Markdown 내용은 반드시 root 설정안에 `locales`의 키들과 같은 이름의 폴더에 위치해야합니다. 다시 말해서: ```js // docs/.vitepress/config.js export default defineConfig({ ... locales: { root: { label: "English", lang: "en-US", themeConfig: { nav: navbars.en, sidebar: sidebars.en, }, }, "pt-br": { label: "Brazilian Portuguese", lang: "pr-br", themeConfig: { nav: navbars.pt_br, sidebar: sidebars.pt_br, }, }, "zh-hans": { label: "简体中文", lang: "zh-hans", themeConfig: { nav: navbars.zh_hans, sidebar: sidebars.zh_hans, }, }, }, }) ``` `/pt-BR/`는 `docs/pt-BR/`에 위치한 Markdown 파일들의 세트가 똑같이 필요합니다, 예를 들어: ```shell docs ├─ README.md ├─ foo.md ├─ nested │ └─ README.md └─ pt-BR ├─ README.md ├─ foo.md └─ nested └─ README.md ``` 더 자세한 정보는 [공식 VitePress i18n 문서](https://vitepress.dev/guide/i18n)에서 확인 가능합니다. ================================================ FILE: docs/ko-kr/contribute/first-party-plugins.md ================================================ # 공식 플러그인 asdf 코어 팀은 일상 업무 환경에서 사용되는 플러그인들을 작성해왔습니다. 이 플러그인들을 관리하고 발전시키는 도움은 언제든 환영입니다. 아래 각 링크들에서 관련된 리포지토리들을 확인하세요: - [Elixir](https://github.com/asdf-vm/asdf-elixir) - [Erlang](https://github.com/asdf-vm/asdf-erlang) - [Node.js](https://github.com/asdf-vm/asdf-nodejs) - [Ruby](https://github.com/asdf-vm/asdf-ruby) 커뮤니티 플러그인 보기: - [`asdf-community` 공동체](https://github.com/asdf-community): `asdf` 플러그인의 장기 유지보수를 위한 공동체 주도의 프로젝트입니다. - [`asdf-plugins` shortname 리포지토리](https://github.com/asdf-vm/asdf-plugins): 인기 있는 `asdf` 플러그인 검색을 위해 `asdf` 코어가 사용하는 Short-name 목록. - [Github `asdf-plugin` 토픽 검색](https://github.com/topics/asdf-plugin) ================================================ FILE: docs/ko-kr/contribute/github-actions.md ================================================ # GitHub Actions 당신의 관심에 감사드리며, 존재하는 이슈들, 대화들, 그리고 기여 가이드라인을 [asdf actions 리포지토리](https://github.com/asdf-vm/actions)에서 확인 해주세요. ================================================ FILE: docs/ko-kr/guide/getting-started.md ================================================ # 시작하기 `asdf` 설치는 다음과 같습니다: 1. dependencies 설치 2. `asdf` 코어 다운로드 3. `asdf` 설치 4. 관리하고 싶은 각각의 툴/런타임 플러그인 설치 5. 툴/런타임 버전 설치 6. `.tool-versions` 설정 파일들을 통해 글로벌 혹은 프로젝트 버전들 설정 ## 1. Dependencies 설치 asdf는 `git` & `curl`이 필요합니다. _당신이_ 필요한 패키지 매니저를 구동하기 위한 _일부_ 명령어들의 목록입니다. (몇몇은 나중 단계에서 자동으로 설치될 수 있습니다). | 운영체제 | 패키지 매니저 | 명령어 | | -------- | ------------- | ---------------------------------- | | linux | Aptitude | `apt install curl git` | | linux | DNF | `dnf install curl git` | | linux | Pacman | `pacman -S curl git` | | linux | Zypper | `zypper install curl git` | | macOS | Homebrew | `brew install coreutils curl git` | | macOS | Spack | `spack install coreutils curl git` | ::: tip 노트 시스템 설정에 의해 `sudo`가 필요할 수 있습니다. ::: ## 2. asdf 다운로드 ### 공식 다운로드 <!-- x-release-please-start-version --> ```shell git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.18.1 ``` <!-- x-release-please-end --> ### 커뮤니티 지원 다운로드 방법 공식 `git` 방식을 사용할 것을 적극적으로 권장드립니다. | 방법 | 명령어 | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Homebrew | `brew install asdf` | | Pacman | `git clone https://aur.archlinux.org/asdf-vm.git && cd asdf-vm && makepkg -si` 혹은 선호하시는 [AUR helper](https://wiki.archlinux.org/index.php/AUR_helpers) 사용 | ## 3. asdf 설치 설정에 영향을 미치는 다양한 셸, 운영체제들 & 설치방법의 조합들이 있습니다. 아래 선택사항들 중 가장 적합한 것을 사용하세요. **macOS 사용자들은 이 섹션 마지막 부분에 `path_helper`에 경고를 반드시 읽어보세요.** ::: details Bash & Git 다음을 `~/.bashrc`에 추가하세요: ```shell . "$HOME/.asdf/asdf.sh" ``` 자동완성 설정은 다음을 `.bashrc`에 추가하세요: ```shell . "$HOME/.asdf/completions/asdf.bash" ``` ::: ::: details Bash & Git (macOS) **macOS Catalina 혹은 그 이상**을 사용하신다면, 기본 셸이 **ZSH**로 변경되었습니다. Bash로 다시 변경하지 않으셨다면, ZSH의 설치방법을 따라주세요. 다음을 `~/.bash_profile`에 추가하세요: ```shell . "$HOME/.asdf/asdf.sh" ``` 자동완성 설정은 다음을 `.bash_profile`에 직접 추가하세요: ```shell . "$HOME/.asdf/completions/asdf.bash" ``` ::: ::: details Bash & Homebrew `~/.bashrc`에 `asdf.sh`를 추가하세요: ```shell echo -e "\n. \"$(brew --prefix asdf)/libexec/asdf.sh\"" >> ~/.bashrc ``` 자동완성은 [Homebrew'의 방법에 따라 설정되어야 합니다](https://docs.brew.sh/Shell-Completion#configuring-completions-in-bash) 혹은 다음을 이용하세요: ```shell echo -e "\n. \"$(brew --prefix asdf)/etc/bash_completion.d/asdf.bash\"" >> ~/.bashrc ``` ::: ::: details Bash & Homebrew (macOS) **macOS Catalina 혹은 그 이상**을 사용하신다면, 기본 셸이 **ZSH**로 변경되었습니다. Bash로 다시 변경하지 않으셨다면, ZSH의 설치방법을 따라주세요. `~/.bash_profile`에 `asdf.sh` 추가하기: ```shell echo -e "\n. \"$(brew --prefix asdf)/libexec/asdf.sh\"" >> ~/.bash_profile ``` 자동완성은 [Homebrew'의 방법에 따라 설정되어야 합니다](https://docs.brew.sh/Shell-Completion#configuring-completions-in-bash) 혹은 다음을 이용하세요: ```shell echo -e "\n. \"$(brew --prefix asdf)/etc/bash_completion.d/asdf.bash\"" >> ~/.bash_profile ``` ::: ::: details Bash & Pacman 다음을 `~/.bashrc`에 추가하세요: ```shell . /opt/asdf-vm/asdf.sh ``` 자동완성을 위해 [`bash-completion`](https://wiki.archlinux.org/title/bash#Common_programs_and_options)이 설치 되어야합니다. ::: ::: details Fish & Git 다음을 `~/.config/fish/config.fish`에 추가하세요: ```shell source ~/.asdf/asdf.fish ``` 다음 명령어로 자동완성을 설정하세요: ```shell mkdir -p ~/.config/fish/completions; and ln -s ~/.asdf/completions/asdf.fish ~/.config/fish/completions ``` ::: ::: details Fish & Homebrew `~/.config/fish/config.fish`에 `asdf.fish`를 추가하세요: ```shell echo -e "\nsource "(brew --prefix asdf)"/libexec/asdf.fish" >> ~/.config/fish/config.fish ``` 자동완성은 [Fish 셸 Homebrew에 의해 관리됩니다](https://docs.brew.sh/Shell-Completion#configuring-completions-in-fish). 편하죠! ::: ::: details Fish & Pacman 다음을 `~/.config/fish/config.fish`에 추가하세요: ```shell source /opt/asdf-vm/asdf.fish ``` 자동완성은 AUR 패키지를 통한 설치로 자동적으로 설정됩니다. ::: ::: details Elvish & Git `~/.config/elvish/rc.elv`에 `asdf.elv`를 추가하세요: ```shell mkdir -p ~/.config/elvish/lib; ln -s ~/.asdf/asdf.elv ~/.config/elvish/lib/asdf.elv echo "\n"'use asdf _asdf; var asdf~ = $_asdf:asdf~' >> ~/.config/elvish/rc.elv echo "\n"'set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~' >> ~/.config/elvish/rc.elv ``` 자동완성은 자동적으로 설정됩니다. ::: ::: details Elvish & Homebrew Add `asdf.elv` to your `~/.config/elvish/rc.elv` with: ```shell mkdir -p ~/.config/elvish/lib; ln -s (brew --prefix asdf)/libexec/asdf.elv ~/.config/elvish/lib/asdf.elv echo "\n"'use asdf _asdf; var asdf~ = $_asdf:asdf~' >> ~/.config/elvish/rc.elv echo "\n"'set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~' >> ~/.config/elvish/rc.elv ``` 자동완성은 자동적으로 설정됩니다. ::: ::: details Elvish & Pacman `~/.config/elvish/rc.elv`에 `asdf.elv`를 추가하세요: ```shell mkdir -p ~/.config/elvish/lib; ln -s /opt/asdf-vm/asdf.elv ~/.config/elvish/lib/asdf.elv echo "\n"'use asdf _asdf; var asdf~ = $_asdf:asdf~' >> ~/.config/elvish/rc.elv echo "\n"'set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~' >> ~/.config/elvish/rc.elv ``` 자동완성은 자동적으로 설정됩니다. ::: ::: details ZSH & Git 다음을 `~/.zshrc`에 추가하세요: ```shell . "$HOME/.asdf/asdf.sh" ``` **혹은** 위 스크립트와 자동완성을 설정하는 [asdf를 위한 oh-my-zsh](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/asdf)와 같은 ZSH 프레임워크 플러그인을 사용하세요. 자동완성은 ZSH 프레임워크 `asdf` 플러그인 혹은 다음을 `.zshrc`에 추가함으로써 설정됩니다: ```shell # append completions to fpath fpath=(${ASDF_DIR}/completions $fpath) # initialise completions with ZSH's compinit autoload -Uz compinit && compinit ``` - 만약 custom `compinit` 설정을 사용중이라면, `asdf.sh`를 source하고 난 다음 `compinit`가 오도록 해주세요 - 만약 ZSH 프레임워크를 통해 custom `compinit` 설정을 사용중이라면, 해당 프레임워크를 source하고 난 다음 `compinit`가 오도록 해주세요 **경고** 만약 ZSH 프레임워크를 사용중이라면, `fpath`를 통해 새로운 ZSH 자동완성을 사용하려면 관련된 `asdf` 플러그인이 업데이트 되어야합니다. Oh-My-ZSH asdf 플로그인이 아직 업데이트 되지 않았습니다, [ohmyzsh/ohmyzsh#8837](https://github.com/ohmyzsh/ohmyzsh/pull/8837) 참고. ::: ::: details ZSH & Homebrew `~/.zshrc`에 `asdf.sh`를 추가하세요: ```shell echo -e "\n. $(brew --prefix asdf)/libexec/asdf.sh" >> ${ZDOTDIR:-~}/.zshrc ``` **혹은** 위 스크립트와 자동완성을 설정하는 [asdf를 위한 oh-my-zsh](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/asdf)와 같은 ZSH 프레임워크 플러그인을 사용하세요. 자동완성은 `asdf` ZSH 프레임워크 혹은 [Homebrew'의 방법](https://docs.brew.sh/Shell-Completion#configuring-completions-in-zsh)에 따라 설정되어야 합니다. 만약 ZSH 프레임워크를 사용중이라면, `fpath`를 통해 새로운 ZSH 자동완성을 사용하려면 관련된 asdf 플러그인이 업데이트 되어야합니다. Oh-My-ZSH asdf 플로그인이 아직 업데이트 되지 않았습니다, [ohmyzsh/ohmyzsh#8837](https://github.com/ohmyzsh/ohmyzsh/pull/8837) 참고. ::: ::: details ZSH & Pacman 다음을 `~/.zshrc`에 추가하세요: ```shell . /opt/asdf-vm/asdf.sh ``` 자동완성은 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 `~/.config/powershell/profile.ps1`에 `asdf.sh`를 추가하세요: ```shell 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 `~/.config/nushell/config.nu`에 `asdf.nu`를 추가하세요: ```shell "\n$env.ASDF_DIR = ($env.HOME | path join '.asdf')\n source " + ($env.HOME | path join '.asdf/asdf.nu') | save --append $nu.config-path ``` 자동완성은 자동적으로 설정됩니다. ::: ::: details Nushell & Homebrew `~/.config/nushell/config.nu`에 `asdf.nu`를 추가하세요: ```shell "\n$env.ASDF_DIR = (brew --prefix asdf | str trim | into string | path join 'libexec')\n source " + (brew --prefix asdf | str trim | into string | path join 'libexec/asdf.nu') | save --append $nu.config-path ``` 자동완성은 자동적으로 설정됩니다. ::: ::: details Nushell & Pacman `~/.config/nushell/config.nu`에 `asdf.nu`를 추가하세요: ```shell "\n$env.ASDF_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 `~/.profile`에 `asdf.sh`를 추가하세요: ```shell 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 등) source **이후에** source 되어야 합니다. `PATH` 업데이트를 위해 셸을 재시작하세요. 새로운 터미널을 여는 경우 대부분 해결됩니다. ## 코어 설치 완료! `asdf` 코어 설치를 완료했습니다 :tada: `asdf`는 **플러그인**과 **툴**을 설치하고, **버전**들을 관리해야 유용합니다. 설치 및 관리방법을 이 가이드 아래에서 계속해서 배우세요. ## 4. 플러그인 설치 데모 목적으로 우리는 [`asdf-nodejs`](https://github.com/asdf-vm/asdf-nodejs/) 플러그인을 통해 [Node.js](https://nodejs.org/) 설치 & 설정을 해보겠습니다. ### 플러그인 Dependencies 각 플러그인은 dependencies 갖고 있어 우리는 플러그인 리포지토리의 목록을 확인해야합니다. `asdf-nodejs`는 다음을 가지고 있습니다: | OS | Dependency 설치 | | ------------------------------ | --------------------------------------- | | Debian | `apt-get install dirmngr gpg curl gawk` | | CentOS/ Rocky Linux/ AlmaLinux | `yum install gnupg2 curl gawk` | | macOS | `brew install gpg gawk` | 우리는 어떤 플러그인들은 설치-후 훅들을 갖고있어 dependencies 먼저 설치해야합니다. ### 플러그인 설치 ```shell asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git ``` ## 5. 버전 설치 이제 우리는 Node.js를 위한 플러그인을 갖고있어 툴 버전을 설치할 수 있습니다. 우리는 `asdf list all nodejs`를 통해 어떤 버전들이 이용가능한지 혹은 `asdf list all nodejs 14`를 통해 하위 버전들을 확인가능합니다. 우리는 이용가능한 `latest` 버전을 설치할 것입니다. ```shell asdf install nodejs latest ``` ::: tip 노트 `asdf`는 정확한 버전들을 강제합니다. `latest`는 `asdf`가 실행했을때 실제 버전을 찾는 헬퍼입니다. ::: ## 6. 버전 설정 `asdf`는 현재 작업 디렉토리부터 `$HOME` 디렉토리까지 모든 `.tool-versions` 파일들에서 버전 검색을 수행합니다. 검색은 `asdf`가 관리하는 툴을 실행시킬때 맞춰서 실행됩니다. ::: 경고 툴 실행을 위한 툴 버전을 설정하지 않으면 **에러**가 발생합니다. `asdf current`는 현재 디렉토리로부터 툴 & 버전을 표시함으로써 어떤 툴들이 실행을 실패하는지 관찰할 수 있게합니다. ::: ### 글로벌 글로벌 기본값들은 `$HOME/.tool-versions`에서 관리됩니다. 글로벌 버전을 다음을 이용해 설정하세요: ```shell asdf set --home nodejs 16.5.0 ``` `$HOME/.tool-versions`은 다음과 같습니다: ``` nodejs 16.5.0 ``` 어떤 운영체제들은 `asdf`가 아닌 시스템에 의해 관리되는 툴들이 이미 설치되어 있습니다, `python`이 대표적인 예시입니다. 당신은 시스템에 의한 툴 관리를 위해 `asdf`를 설정해야합니다. [버전 참조 섹션](/ko-kr/manage/versions.md)를 참고하세요. ### 로컬 로컬 버전들은 (현재 작업 디렉토리) `$PWD/.tool-versions` 파일에 정의 되어 있습니다. 보통, 이 디렉토리는 하나의 프로젝트의 Git 리포지토리입니다. 툴 버전을 설정하고 싶은 디렉토리에서 다음을 실행시키세요: ```shell asdf set nodejs latest ``` `$PWD/.tool-versions`은 다음과 같습니다: ``` nodejs 16.5.0 ``` ### 기존의 툴 버전 파일들 사용하기 `asdf`는 기존의 다른 버전 매니저들의 버전 파일들 마이그레이션을 지원합니다. 예시: `rbenv`의 `.ruby-version`. 이는 각 플러그인 기준으로 지원됩니다. [`asdf-nodejs`](https://github.com/asdf-vm/asdf-nodejs/)는 `.nvmrc`와 `.node-version` 파일들을 지원합니다. 이를 활성화하기 위해, 다음을 `asdf` 설정 파일 `$HOME/.asdfrc`에 추가하세요: ``` legacy_version_file = yes ``` 더 많은 설정 옵션들은 [configuration](/ko-kr/manage/configuration.md) 페이지를 참고하세요. ## 가이드 끝! `asdf` 시작하기 가이드가 끝났습니다. :tada: 당신은 이제 당신의 프로젝트의 `nodejs` 버전들을 관리할 수 있습니다. 같은 방법으로 다른 각각의 툴들의 버전을 관리하세요! `asdf`는 우리가 익숙해져야하는 더 많은 명령어들을 가지고 있고, `asdf --help` 혹은 `asdf`를 통해 확인할 수 있습니다. 명령어들의 코어의 3가지 카테고리로 나눠질 수 있습니다: - [코어 `asdf`](/ko-kr/manage/core.md) - [플러그인](/ko-kr/manage/plugins.md) - [툴 버전](/ko-kr/manage/versions.md) ================================================ FILE: docs/ko-kr/guide/introduction.md ================================================ # 소개 `asdf`는 툴 버전 매니저입니다. 모든 툴 버전 정의들은 당신의 팀들과 공유되는 프로젝트의 Git 리포지토리에서 확인할 수 있는 하나의 (`.tool-versions`) 파일에 있으며, 모든 사람들이 **정확히** 같은 버전의 툴들을 사용하게 합니다. 기존의 작업 방식은 여러 CLI 버전 매니저들, 각각의 고유한 API, 설정 파일들 그리고 구현이 필요했었습니다 (e.g. `$PATH` 조정, shims, 환경 변수 등...). `asdf`는 개발 워크플로우 단순화를 위해 단 하나의 인터페이스와 설정파일을 제공하고 단순한 플러그인 인터페이스를 통해 모든 툴과 런타임들 확장가능합니다. ## 작동방식 `asdf` 코어가 셸 설정과 함께 설치되면, 플러그인들이 특정 툴들을 관리하기 위해 설치됩니다. 플러그인에 의해 한가지 툴이 설치되면, [shims](<https://en.wikipedia.org/wiki/Shim_(computing)>)들을 가진 실행파일들이 각각의 툴들을 위해 생성됩니다. 실행파일들을 실행하려 할 때, `.tool-versions`에 설정된 툴의 버전을 통해 `asdf`가 어떤 버전을 실행시킬 지 결정하고 해당 shim이 대신 실행됩니다. ## 관련된 프로젝트 ### nvm / n / rbenv 등 [nvm](https://github.com/nvm-sh/nvm), [n](https://github.com/tj/n) 그리고 [rbenv](https://github.com/rbenv/rbenv)과 같은 툴들은 설치된 실행파일을 위한 shim들을 만드는 셸 스크립트들로 작성되어 있습니다. `asdf`는 매우 비슷하고 툴/런타임 버전 관리의 영역에서 경쟁하기 위해 만들어졌습니다. `asdf`의 차별화 요소는 플러그인 시스템이 툴/런타임 별 매니저의 필요성, 각기 다른 명령어들, 그리고 리포지토리에 각각 `*-version` 파일들을 제거하였다는 것입니다. <!-- ### pyenv TODO: someone with Python background expand on this `asdf` has some similarities to `pyenv` but is missing some key features. The `asdf` team is looking at introducing some of these `pyenv` specific features, though no roadmap or timeline is available. --> ### direnv > 현재 디렉토리에 따라 환경 변수들을 load와 unload 할 수 있는 새로운 기능을 기존의 셸에 추가합니다. `asdf`는 환경 변수들을 관리하지 않습니다만, [`asdf-direnv`](https://github.com/asdf-community/asdf-direnv) 플러그인을 통해 direnv 동작를 `asdf`에 통합할 수 있습니다. [direnv 문서](https://direnv.net/)에서 더보기. ### Homebrew > macOS (혹은 Linux)에서의 패키지 매니저 부재 Homebrew는 패키지들과 upstream dependencies들을 관리합니다. `asdf`는 upstream dependencies들을 관리하지 않고, 패키지 매니저가 아니고, 우리가 dependency 목록들을 작게 유지하므로, 사용자가 직접 관리해야합니다. [Homebrew 문서](https://brew.sh/)에서 더보기. ### NixOS > Nix는 패키지 관리와 시스템 설정에 창의적으로 접근하는 툴입니다. NixOS는, `asdf`가 제공하지 않는, 각 툴의 전체 dependency tree를 통해 패키지들의 정확한 버전들을 관리함으로써 재현가능한 환경 구축을 목표로 합니다. NixOS는 자신만의 프로그래밍 언어, 많은 CLI 툴들, 그리고 6000개가 넘는 패키지 컬렉션을 통해 해당 기능을 제공합니다. 다시 한번 말씀드리지만, `asdf`는 upstream dependencies들을 관리하지 않으며 패키지 매니저가 아닙니다. [NixOS 문서](https://nixos.org/guides/how-nix-works.html)에서 더보기. ## 왜 asdf를 사용할까요? `asdf`는 팀들이 플러그인 시스템을 통해 **다양한** 툴들의 지원 그리고 셸 설정에 포함시킬 하나의 **셸** 스크립트의 _단순함_ 과 _친숙성_ 을 통해 **정확히** 같은 버전의 툴들을 사용하는 것을 보장합니다. ::: tip 노트 `asdf`는 시스템 패키지 매니저가 아닙니다. 이것은 툴 버전 매니저입니다. 단지 어떠한 툴을 위한 플러그인을 생성하고 그것의 버전을 `asdf`로 관리할 수 있다고 해서, 그 특정한 툴을 버전 관리를 위한 최선의 방법을 의미하는 것은 아닙니다. ::: ================================================ FILE: docs/ko-kr/index.md ================================================ --- # https://vitepress.dev/reference/default-theme-home-page layout: home hero: name: asdf text: 다중 런타임 버전 매니저 tagline: 한가지 툴로 모든 런타임 버전들을 관리하세요! actions: - theme: brand text: 시작하기 link: /ko-kr/guide/getting-started - theme: alt text: asdf이란? link: /ko-kr/guide/introduction - theme: alt text: GitHub에서 보기 link: https://github.com/asdf-vm/asdf features: - title: "단 한가지 도구" details: "각 프로젝트 런타임들을 단 한가지 CLI 툴과 커맨드 인터페이스로 관리." icon: 🎉 - title: "플러그인" details: "런타임과 툴들의 거대한 생태계. 당신이 필요한 새로운 툴들을 더해주는 간단한 API!" icon: 🔌 - title: "구버전 호환" details: "원활한 마이그레이션을 위해 이미 존재하던 .nvmrc, .node-version, .ruby-version 등의 설정 파일들 지원!" icon: ⏮ - title: "단 하나의 설정 파일" details: "단 하나의 공유된 .tool-versions 파일로 모든 툴, 런타임, 그리고 버전들을 관리." icon: 📄 - title: "셸" details: "Bash, ZSH, Fish & Elvish 자동완성 기능 지원." icon: 🐚 - title: "GitHub Actions" details: "GitHub Action 설치 제공과 .tool-versions 파일을 CI/CD 워크플로우에서 활용." icon: 🤖 --- ================================================ FILE: docs/ko-kr/manage/commands.md ================================================ # 모든 명령어 다음 목록은 `asdf`에서 이용가능한 모든 명령어들입니다. 해당 목록은 `asdf help` 명령어를 통해 확인가능합니다. <<< @../../internal/help/help.txt ================================================ FILE: docs/ko-kr/manage/configuration.md ================================================ # 설정 `asdf`의 설정은 공유가능한 `.tool-versions` 파일들 뿐만 아니라 `.asdfrc`를 통한 특정한 사용자 맞춤화 및 환경 변수들을 모두 포함합니다. ## `.tool-versions` 한 디렉토리에 `.tool-versions` 파일이 존재하면, 해당 파일에 정의된 툴 버전들은 해당 디렉토리와 모든 하위 디렉토리에서 사용됩니다. ::: warning 노트 글로벌 기본값들은 `$HOME/.tool-versions` 파일에 설정 가능합니다 ::: `.tool-versions` 파일의 형태는 다음과 같습니다: ``` ruby 2.5.3 nodejs 10.15.0 ``` 다음과 같이 주석을 넣을 수 있습니다: ``` ruby 2.5.3 # This is a comment # This is another comment nodejs 10.15.0 ``` 버전들은 다음과 같은 형식일 수 있습니다: - `10.15.0` - 실제 버전. 바이너리 다운로드를 지원하는 플러그인은 바이너리를 다운로드합니다. - `ref:v1.0.2-a` 혹은 `ref:39cb398vb39` - 지정된 태그/커밋/브랜치 Github로부터 다운로드하고 컴파일됩니다. - `path:~/src/elixir` - 사용하려는 툴의 맞춤 컴파일 버전을 위한 경로. 언어 개발자들 등이 사용합니다. - `system` - 이 키워드는 asdf가 asdf에 의해 관리되지 않는 시스템 버전 툴의 버전을 사용하게합니다. ::: tip 다양한 버전들은 공백으로 구분하여 설정될 수 있습니다. 예를 들어, 파이썬 `3.7.2`를 사용하고, 파이썬 `2.7.15`로 그리고 마지막으로 `system` 파이썬으로 폴백하려면, 다음을 `.tool-versions`에 추가해주세요. ``` python 3.7.2 2.7.15 system ``` ::: `.tool-version` 파일에 정의된 모든 툴들을 설치하려면 `.tool-version` 파일이 포함된 디렉토리에서 다른 인수 없이 `asdf install`을 실행합니다. `.tool-versions` 파일에 정의된 하나의 툴을 설치하려면 `.tool-version` 파일이 포함된 디렉토리에서 `asdf install <name>`를 실행합니다. 이 툴은 `.tool-versions` 파일에 정의된 버전으로 설치됩니다. 해당 파일은 직접 편집하거나 `asdf set` 명령어(또는 `asdf set --home` 명령어)를 사용하여 업데이트해 주세요. ## `.asdfrc` `.asdfrc` 파일은 사용자의 머신별 설정을 정의합니다. `${HOME}/.asdfrc`는 asdf가 사용하는 기본 위치입니다. 이는 [환경 변수 `ASDF_CONFIG_FILE`](#asdf-config-file)로 설정 가능합니다. 아래 파일은 필수적인 형식과 기본값들을 보여줍니다: ```txt legacy_version_file = no use_release_candidates = no always_keep_download = no plugin_repository_last_check_duration = 60 disable_plugin_short_name_repository = no concurrency = auto ``` ### `legacy_version_file` **지원되는** 플러그인들은 다른 버전 매니저에서 사용되는 버전 파일들을 읽을 수 있습니다, 예를 들어, 루비의 `rbenv`에서 `.ruby-version`. | 옵션 | 설명 | | :------------------------------------------------------ | :------------------------------------------------------------------------------------------ | | `no` <Badge type="tip" text="기본" vertical="middle" /> | 버전을 불러오는 데는 `.tool-versions`를 사용합니다 | | `yes` | 이용 가능한 레거시 버전 파일(`.ruby-version` 등)이 있는 경우 플러그인의 폴백으로 사용합니다 | ### `always_keep_download` `asdf install` 명령어로 다운로드하는 소스 코드 또는 바이너리를 유지 또는 제거하도록 설정합니다 | 옵션 | 설명 | | :------------------------------------------------------ | :-------------------------------------------- | | `no` <Badge type="tip" text="기본" vertical="middle" /> | 성공적인 설치 후 소스 코드 또는 바이너리 제거 | | `yes` | 설치 후 소스 코드 또는 바이너리 유지 | ### `plugin_repository_last_check_duration` asdf 플러그인 리포지토리 동기화 간격(분)을 설정합니다. 트리거 이벤트는 지난 동기화 시간을 확인하게 합니다. 마지막 동기화 이후 지정된 동기화 간격보다 더 많은 시간이 경과하면, 새로운 동기화가 발생합니다. | 옵션 | 설명 | | :-------------------------------------------------------------------------------------------- | :------------------------------------------------------------------ | | `1`에서 `999999999` 사이의 정수 <br/> `60` <Badge type="tip" text="기본" vertical="middle" /> | 마지막 동기화 이후 지속 시간(분)이 초과된 경우 트리거 이벤트 동기화 | | `0` | 각 트리거 이벤트에서 동기화 | | `never` | 동기화 하지 않음 | 동기화 이벤트는 다음 명령어들을 실행할 때 발생합니다: - `asdf plugin add <name>` - `asdf plugin list all` `asdf plugin add <name> <git-url>` 플러그인 동기화를 트리거하지 않습니다. ::: warning 노트 해당 값을 `never`로 설정하는 것은 플러그인 리포지토리의 초기 동기화를 막지 않고, 해당 기능을 위해 `disable_plugin_short_name_repository`를 참조하세요. ::: ### `disable_plugin_short_name_repository` asdf 플러그인 short-name 리포지토리의 동기화를 비활성화합니다. short-name 리포지토리가 비활성화 되어있으면 동기화 이벤트가 조기 종료됩니다. | 옵션 | 설명 | | :------------------------------------------------------ | :------------------------------------------------------------- | | `no` <Badge type="tip" text="기본" vertical="middle" /> | 동기화 이벤트에서 asdf 플러그인 리포지토리 clone 또는 업데이트 | | `yes` | short-name 플러그인 리포지토리 비활성화 | 동기화 이벤트는 다음 명령어들을 실행할 때 발생합니다: - `asdf plugin add <name>` - `asdf plugin list all` `asdf plugin add <name> <git-url>`는 플러그인 동기화를 트리거하지 않습니다. ::: warning 노트 플러그인 short-name repository를 비활성화해도 리포지토리가 이미 동기화된 경우 제거되지 않습니다. `rm --recursive --trash $ASDF_DATA_DIR/repository`로 플러그인 리포지토리를 제거합니다. 플러그인 short-name 리포지토리를 비활성화해도 그 리포지토리로부터 설치된 이전의 플러그인은 제거되지 않습니다. `asdf plugin remove <name>`을 사용하여 플러그인을 제거할 수 있습니다. 플러그인을 제거하면 해당 툴의 모든 설치된 버전이 제거됩니다. ::: ### `concurrency` 컴파일 중에 사용할 기본 코어 수입니다. | 옵션 | 설명 | | :----- | :--------------------------------------------------------------------------------------------- | | 정수 | 소스 코드를 컴파일할 때 사용할 코어 수 | | `auto` | `nproc`, `sysctl hw.ncpu`, `/proc/cpuinfo` 또는 `1`을 순차적으로 사용하여 코어 수를 계산합니다 | 노트: `ASDF_CONCURRENCY` 환경 변수가 존재하는 경우 우선 순위를 갖습니다. ### 플러그인 훅 다음에서 사용자 맞춤 코드를 실행이 가능합니다: - 플러그인 설치, shim 재생성, 업데이트, 또는 제거 전 또는 후 - 플러그인 명령어 실행 전 또는 후 예를 들어 `foo`라는 플러그인이 설치되어 있고 `bar`라는 실행파일이 제공된 경우, 다음 훅들을 사용하여 사용자 맞춤 코드를 먼저 실행할 수 있습니다: ```text pre_foo_bar = echo Executing with args: $@ ``` 지원되는 패턴은 다음과 같습니다: - `pre_<plugin_name>_<command>` - `pre_asdf_download_<plugin_name>` - `{pre,post}_asdf_{install,reshim,uninstall}_<plugin_name>` - `$1`: 풀 버전 - `{pre,post}_asdf_plugin_{add,update,remove,reshim}` - `$1`: 플러그인 이름 - `{pre,post}_asdf_plugin_{add,update,remove}_<plugin_name>` 어떤 명령어 훅들이 어떤 명령어 이전 또는 이후에 실행되는 지에 대한 자세한 내용은 [플러그인 생성하기](../plugins/create.md)를 참조하세요. ## 환경 변수 환경 변수 설정은 시스템과 셸에 따라 다릅니다. 기본 위치는 설치 위치와 방식(Git clone, Homebrew, AUR)에 달려있습니다. 환경 변수들은 일반적으로 `asdf.sh`/`asdf.fish` 등을 source하기 전에 설정됩니다. Elvish의 경우는, 상단에서 `use asdf`로 설정합니다. 다음은 Bash 셸에서 사용법에 관한 설명입니다. ### `ASDF_CONFIG_FILE` `.asdfrc` 설정 파일의 경로. 임의의 위치로 설정 가능합니다. 절대 경로여야 합니다. - 미설정 시: `$HOME/.asdfrc`가 사용됩니다. - 사용법: `export ASDF_CONFIG_FILE=/home/john_doe/.config/asdf/.asdfrc` ### `ASDF_TOOL_VERSIONS_FILENAME` 툴 이름과 버전을 저장하는 파일의 파일이름입니다. 임의의 유효한 파일 이름이면 됩니다. 일반적으로, `.tool-version` 파일들을 무시하고 싶을 때 해당 값을 설정하세요. - 미설정 시: `.tool-versions`가 사용됩니다. - 사용법: `export ASDF_TOOL_VERSIONS_FILENAME=tool_versions` ### `ASDF_DIR` `asdf` 코어 스크립트의 위치. 임의의 위치로 설정할 수 있습니다. 절대 경로여야 합니다. - 미설정 시: `bin/asdf` 실행파일의 한 단계 상위 디렉토리가 사용됩니다. - 사용법: `export ASDF_DIR=/home/john_doe/.config/asdf` ### `ASDF_DATA_DIR` `asdf`가 플러그인, shim들, 툴 버전들을 설치하는 위치. 임의의 위치로 설정할 수 있습니다. 절대 경로여야 합니다. - 미설정 시: `$HOME/.asdf` 존재 시 사용, 존재하지 않는 경우 `ASDF_DIR` 사용 - 사용법: `export ASDF_DATA_DIR=/home/john_doe/.asdf` ### `ASDF_CONCURRENCY` 소스 코드를 컴파일할 때 사용할 코어 수입니다. 설정하면 이 값이 asdf 설정 `concurrency` 값보다 우선 시 됩니다. - 미설정 시: asdf 설정 `concurrency` 값이 사용됩니다. - 사용법: `export ASDF_CONCURRENCY=32` ## 전체 설정의 예시 다음을 이용한 간단한 asdf 설치는: - Bash 셸 - `$HOME/.asdf` 설치 위치 - Git을 통한 설치 - 환경 변수 설정 없음 - 맞춤 `.asdfrc` 파일 없음 다음의 결과가 나오게 됩니다: | 항목 | 값 | 값이 세팅되는 과정 | | :------------------------------------ | :--------------- | :--------------------------------------------------------------------------------------------------------------------------- | | config file location | `$HOME/.asdfrc` | `ASDF_CONFIG_FILE`가 비었으므로, `$HOME/.asdfrc`을 사용 | | default tool versions filename | `.tool-versions` | `ASDF_TOOL_VERSIONS_FILENAME`가 비었으므로, `.tool-versions`을 사용 | | asdf dir | `$HOME/.asdf` | `ASDF_DIR`가 비었으므로, `bin/asdf`의 한 단계 상위 디렉토리 사용 | | asdf data dir | `$HOME/.asdf` | `ASDF_DATA_DIR`가 비었으므로, `$HOME/.asdf`를 `$HOME`으로 사용. | | concurrency | `auto` | `ASDF_CONCURRENCY`가 비었으므로, [기본 설정](https://github.com/asdf-vm/asdf/blob/master/defaults)의 `concurrency` 값에 의존 | | legacy_version_file | `no` | 맞춤 `.asdfrc` 없음, [기본 설정](https://github.com/asdf-vm/asdf/blob/master/defaults) 사용 | | use_release_candidates | `no` | 맞춤 `.asdfrc` 없음, [기본 설정](https://github.com/asdf-vm/asdf/blob/master/defaults) 사용 | | always_keep_download | `no` | 맞춤 `.asdfrc` 없음, [기본 설정](https://github.com/asdf-vm/asdf/blob/master/defaults) 사용 | | plugin_repository_last_check_duration | `60` | 맞춤 `.asdfrc` 없음, [기본 설정](https://github.com/asdf-vm/asdf/blob/master/defaults) 사용 | | disable_plugin_short_name_repository | `no` | 맞춤 `.asdfrc` 없음, [기본 설정](https://github.com/asdf-vm/asdf/blob/master/defaults) 사용 | ================================================ FILE: docs/ko-kr/manage/core.md ================================================ # 코어 코어 `asdf` 명령어는 소수지만, 많은 워크플로우를 원활하게 만들어줍니다. ## 설치 & 설정 [시작하기](/ko-kr/guide/getting-started.md)의 가이드에 설명되어 있습니다. ## 실행 ```shell asdf exec <command> [args...] ``` 현재 버전의 shim 명령어를 실행합니다. <!-- TODO: expand on this with example --> ## 환경 변수 ```shell asdf env <command> [util] ``` <!-- TODO: expand on this with example --> ## 정보 ```shell asdf info ``` 운영체제, 셸 및 `asdf` 디버깅 정보를 출력하는 헬퍼 명령어입니다. 버그 리포트 작성시 공유해주세요. ## Shim 재생성 <a id='Shim-재생성'></a> ```shell asdf reshim <name> <version> ``` 패키지의 현재 버전 shim을 재생성합니다. 기본적으로, shim들은 플러그인을 통해 툴 설치 중에 생성됩니다. [npm CLI](https://docs.npmjs.com/cli/) 등과 같은 툴들은 실행파일을 글로벌 설치할 수 있습니다, 예를 들어, `npm install -g yarn`을 통한 [Yarn](https://yarnpkg.com/) 설치. 이러한 실행파일은 플러그인의 라이프사이클을 통해 설치되지 않았기 때문에, 해당 플러그인을 위한 shim이 아직 존재하지 않습니다. 이때, `nodejs`의 `<version>`에 대해서, 예를 들면 `yarn`과 같은, 새로운 실행파일의 shim을 `asdf reshim nodejs <version>`을 통해 강제적으로 재작성 할 수 있습니다. ## Shim 버전 ```shell asdf shimversions <command> ``` shim을 제공하는 플러그인 및 버전들을 나열합니다. 예를 들면, [Node.js](https://nodejs.org/)에는 `node`와 `npm`이라고 하는 2개의 실행파일이 제공되고 있습니다. [`asdf-nodejs`](https://github.com/asdf-vm/asdf-nodejs/)을 통해 여러 버전의 툴이 설치되어 있는 경우, `shimversions`는 아래와 같은 내용을 출력할 수 있습니다: ```shell ➜ asdf shimversions node nodejs 14.8.0 nodejs 14.17.3 nodejs 16.5.0 ``` ```shell ➜ asdf shimversions npm nodejs 14.8.0 nodejs 14.17.3 nodejs 16.5.0 ``` ## 업데이트 `asdf`를 설치하는 데 사용한 것과 같은 방법을 사용하여 업데이트하세요. `asdf`의 최신 버전은 이 페이지의 오른쪽 상단 모서리에 표시됩니다. ## 제거 `asdf` 제거를 위해 다음 절차를 따르세요: ::: details Bash & Git 1. `~/.bashrc`에서, `asdf.sh` 및 자동완성을 source하고 있는 행들을 삭제: ```shell . "$HOME/.asdf/asdf.sh" . "$HOME/.asdf/completions/asdf.bash" ``` 2. `$HOME/.asdf` 디렉토리 제거: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 3. 모든 `asdf` 설정 파일들 제거를 위해 아래 명령어 실행: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Bash & Git (macOS) 1. `~/.bash_profile`에서, `asdf.sh` 및 자동완성을 source하고 있는 행들을 삭제: ```shell . "$HOME/.asdf/asdf.sh" . "$HOME/.asdf/completions/asdf.bash" ``` 2. `$HOME/.asdf` 디렉토리 제거: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 3. 모든 `asdf` 설정 파일들 제거를 위해 아래 명령어 실행: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Bash & Homebrew 1. `~/.bashrc`에서, `asdf.sh` 및 자동완성을 source하고 있는 행들을 삭제: ```shell . $(brew --prefix asdf)/libexec/asdf.sh . $(brew --prefix asdf)/etc/bash_completion.d/asdf.bash ``` 명령어 자동완성에 대해서는 [Homebrew에 설명되어 있는 방법으로 설정](https://docs.brew.sh/Shell-Completion#configuring-completions-in-bash) 되어 있을 가능성이 있으므로, 그 가이드에 따라 삭제할 행을 찾아주세요. 2. 패키지 관리자를 사용하여 제거: ```shell brew uninstall asdf --force ``` 3. 모든 `asdf` 설정 파일들 제거를 위해 아래 명령어 실행: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Bash & Homebrew (macOS) **macOS Catalina 혹은 그 이상**을 사용하신다면, 기본 셸이 **ZSH**로 변경되었습니다. 만약, `~/.bash_profile`에서 설정을 찾을 수 없는 경우는, `~/.zshrc`에 있을 가능성이 있는데 이 경우 ZSH의 설명을 봐 주세요. 1. `~/.bash_profile`에서, `asdf.sh` 및 자동완성을 source하고 있는 행들을 삭제: ```shell . $(brew --prefix asdf)/libexec/asdf.sh . $(brew --prefix asdf)/etc/bash_completion.d/asdf.bash ``` 명령어 자동완성에 대해서는 [Homebrew에 설명되어 있는 방법으로 설정](https://docs.brew.sh/Shell-Completion#configuring-completions-in-bash) 되어 있을 가능성이 있으므로, 그 가이드에 따라 삭제할 행을 찾아주세요. 2. 패키지 관리자를 사용하여 제거: ```shell brew uninstall asdf --force ``` 3. 모든 `asdf` 설정 파일들 제거를 위해 아래 명령어 실행: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Bash & Pacman 1. `~/.bashrc`에서, `asdf.sh` 및 자동완성을 source하고 있는 행들을 삭제: ```shell . /opt/asdf-vm/asdf.sh ``` 2. 패키지 관리자를 사용하여 제거: ```shell pacman -Rs asdf-vm ``` 3. `$HOME/.asdf` 디렉토리 제거: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 4. 모든 `asdf` 설정 파일들 제거를 위해 아래 명령어 실행: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Fish & Git 1. `~/.config/fish/config.fish`에서, `asdf.fish`를 source하고 있는 행들을 삭제: ```shell source ~/.asdf/asdf.fish ``` 그리고 자동완성을 다음 명령어로 제거: ```shell rm -rf ~/.config/fish/completions/asdf.fish ``` 2. `$HOME/.asdf` 디렉토리 제거: ```shell rm -rf (string join : -- $ASDF_DATA_DIR $HOME/.asdf) ``` 3. 모든 `asdf` 설정 파일들 제거를 위해 아래 명령어 실행: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Fish & Homebrew 1. `~/.config/fish/config.fish`에서, `asdf.fish`를 source하고 있는 행들을 삭제: ```shell source "(brew --prefix asdf)"/libexec/asdf.fish ``` 2. 패키지 관리자를 사용하여 제거: ```shell brew uninstall asdf --force ``` 3. 모든 `asdf` 설정 파일들 제거를 위해 아래 명령어 실행: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Fish & Pacman 1. `~/.config/fish/config.fish`에서, `asdf.fish`를 source하고 있는 행들을 삭제: ```shell source /opt/asdf-vm/asdf.fish ``` 2. 패키지 관리자를 사용하여 제거: ```shell pacman -Rs asdf-vm ``` 3. `$HOME/.asdf` 디렉토리 제거: ```shell rm -rf (string join : -- $ASDF_DATA_DIR $HOME/.asdf) ``` 4. 모든 `asdf` 설정 파일들 제거를 위해 아래 명령어 실행: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Elvish & Git 1. `~/.config/elvish/rc.elv`에서, `asdf` 모듈을 사용하는 행들을 삭제: ```shell use asdf _asdf; var asdf~ = $_asdf:asdf~ set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~ ``` 그리고 `asdf` 모듈을 다음 명령어로 제거: ```shell rm -f ~/.config/elvish/lib/asdf.elv ``` 2. `$HOME/.asdf` 디렉토리 제거: ```shell if (!=s $E:ASDF_DATA_DIR "") { rm -rf $E:ASDF_DATA_DIR } else { rm -rf ~/.asdf } ``` 3. 모든 `asdf` 설정 파일들 제거를 위해 아래 명령어 실행: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Elvish & Homebrew 1. `~/.config/elvish/rc.elv`에서, `asdf` 모듈을 사용하는 행들을 삭제: ```shell use asdf _asdf; var asdf~ = $_asdf:asdf~ set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~ ``` 그리고 `asdf` 모듈을 다음 명령어로 제거: ```shell rm -f ~/.config/elvish/lib/asdf.elv ``` 2. 패키지 관리자를 사용하여 제거: ```shell brew uninstall asdf --force ``` 3. 모든 `asdf` 설정 파일들 제거를 위해 아래 명령어 실행: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Elvish & Pacman 1. `~/.config/elvish/rc.elv`에서, `asdf` 모듈을 사용하는 행들을 삭제: ```shell use asdf _asdf; var asdf~ = $_asdf:asdf~ set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~ ``` 그리고 `asdf` 모듈을 다음 명령어로 제거: ```shell rm -f ~/.config/elvish/lib/asdf.elv ``` 2. 패키지 관리자를 사용하여 제거: ```shell pacman -Rs asdf-vm ``` 3. `$HOME/.asdf` 디렉토리 제거: ```shell if (!=s $E:ASDF_DATA_DIR "") { rm -rf $E:ASDF_DATA_DIR } else { rm -rf ~/.asdf } ``` 4. 모든 `asdf` 설정 파일들 제거를 위해 아래 명령어 실행: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details ZSH & Git 1. `~/.zshrc`에서, `asdf.sh` 및 자동완성을 source하고 있는 행들을 삭제: ```shell . "$HOME/.asdf/asdf.sh" # ... fpath=(${ASDF_DIR}/completions $fpath) autoload -Uz compinit compinit ``` **혹은** 사용된 ZSH 프레임워크 플러그인 제거. 2. `$HOME/.asdf` 디렉토리 제거: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 3. 모든 `asdf` 설정 파일들 제거를 위해 아래 명령어 실행: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details ZSH & Homebrew 1. `~/.zshrc`에서, `asdf.sh`을 source하고 있는 행들을 삭제: ```shell . $(brew --prefix asdf)/libexec/asdf.sh ``` 2. 패키지 관리자를 사용하여 제거: ```shell brew uninstall asdf --force && brew autoremove ``` 3. 모든 `asdf` 설정 파일들 제거를 위해 아래 명령어 실행: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details ZSH & Pacman 1. `~/.zshrc`에서, `asdf.sh`을 source하고 있는 행들을 삭제: ```shell . /opt/asdf-vm/asdf.sh ``` 2. 패키지 관리자를 사용하여 제거: ```shell pacman -Rs asdf-vm ``` 3. `$HOME/.asdf` 디렉토리 제거: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 4. 모든 `asdf` 설정 파일들 제거를 위해 아래 명령어 실행: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: 끝! 🎉 ================================================ FILE: docs/ko-kr/manage/plugins.md ================================================ # 플러그인 플로그인들은 `asdf`가 Node.js, Ruby, Elixir 등 여러가지 툴들을 취급하는 방법입니다. 더 많은 툴들 지원을 위한 플러그인 API는 [플러그인 생성하기](/ko-kr/plugins/create.md) 참고하세요. ## 추가하기 Git URL로 플러그인 추가하기: ```shell asdf plugin add <name> <git-url> # asdf plugin add elm https://github.com/vic/asdf-elm ``` 또는 플러그인 리포지토리에 short-name을 통해 추가하기: ```shell asdf plugin add <name> # asdf plugin add erlang ``` ::: tip 추천 short-name 리포지토리에 독립적인 긴 `git-url` 방식이 선호됩니다. ::: ## 설치된 목록 ```shell asdf plugin list # asdf plugin list # java # nodejs ``` ```shell asdf plugin list --urls # asdf plugin list # java https://github.com/halcyon/asdf-java.git # nodejs https://github.com/asdf-vm/asdf-nodejs.git ``` ## 모든 Short-name 리포지토리 목록 ```shell asdf plugin list all ``` 플러그인들의 전체 short-name 목록을 [플러그인 Shortname 인덱스](https://github.com/asdf-vm/asdf-plugins)에서 확인하세요. ## 업데이트 ```shell asdf plugin update --all ``` 특정 패키지를 업데이트하고 싶다면, 다음 명령어를 사용하세요. ```shell asdf plugin update <name> # asdf plugin update erlang ``` 이 명령어는 해당 플러그인 리포지토리의 _origin_ _기본 브랜치_ 의 _가장 최근 커밋_ 을 fetch합니다. 버전화된 플러그인들과 업데이트들은 현재 개발 진행중 입니다 ([#916](https://github.com/asdf-vm/asdf/pull/916)). ## 제거 ```bash asdf plugin remove <name> # asdf plugin remove erlang ``` 플러그인 제거는 해당 툴과 관련된 모든 것을 제거합니다. 이것은 한 툴의 미사용중인 많은 버전들의 cleaning/pruning에 유용합니다. ## asdf Short-name 리포지토리 동기화 Short-name 리포지토리는 로컬 머신에 주기적으로 동기화됩니다. 동기화 방식들로 다음 방식들이 있습니다: - 명령어들에 의한 동기화 이벤트: - `asdf plugin add <name>` - `asdf plugin list all` - 만약 `disable_plugin_short_name_repository` 설정 옵션이 `yes`로 설정되어 있다면, 동기화는 조기 종료됩니다. [asdf 설정 문서](/ko-kr/manage/configuration.md)에서 더보기. - 만약 동기화가 지난 `X`분 동안 진행되지 않았다면, 동기화가 진행됩니다. - `X`의 기본값은 `60`입니다만, `.asdfrc`의 `plugin_repository_last_check_duration` 옵션을 통해 설정될 수 있습니다. [asdf 설정 문서](/ko-kr/manage/configuration.md)에서 더보기. ================================================ FILE: docs/ko-kr/manage/versions.md ================================================ # 버전 ## 버전 설치 ```shell asdf install <name> <version> # asdf install erlang 17.3 ``` 플러그인이 소스에서 다운로드 & 컴파일을 지원하는 경우, `ref:foo`를 지정할 수 있으며 여기서 `foo`는 특정 브랜치, 태그 또는 커밋입니다. 제거할 때도 동일한 이름과 참조를 사용해야 합니다. ## 최신 안정 버전 설치 ```shell asdf install <name> latest # asdf install erlang latest ``` 주어진 문자열로 시작하는 최신 안정 버전을 설치합니다. ```shell asdf install <name> latest:<version> # asdf install erlang latest:17 ``` ## 설치된 버전 목록 ```shell asdf list <name> # asdf list erlang ``` 주어진 문자열로 시작하는 버전으로 필터링합니다. ```shell asdf list <name> <version> # asdf list erlang 17 ``` ## 사용 가능한 모든 버전 목록 ```shell asdf list all <name> # asdf list all erlang ``` 주어진 문자열로 시작하는 버전으로 필터링합니다. ```shell asdf list all <name> <version> # asdf list all erlang 17 ``` ## 최신 안정 버전 보기 ```shell asdf latest <name> # asdf latest erlang ``` 주어진 문자열로 시작하는 최신 안정 버전을 보여줍니다. ```shell asdf latest <name> <version> # asdf latest erlang 17 ``` ## 현재 버전 설정 <a id='현재-버전-설정'></a> ```shell asdf set [flags] <name> <version> [<version>...] # asdf set elixir 1.2.4 # set in current dir # asdf set -u elixir 1.2.4 # set in .tool-versions file in home directory # asdf set -p elixir 1.2.4 # set in existing .tool-versions file in a parent dir asdf set <name> latest[:<version>] # asdf set elixir latest ``` `asdf set`은 현재 디렉터리에 `.tool-versions` 파일에 버전을 기록하며, 파일이 없으면 새로 생성합니다. 이는 순전히 편의 기능으로, `echo "<tool> <version>" > .tool-versions` 를 실행하는 것과 같다고 생각하면 됩니다. `-u` / `--home` 플래그를 사용하면 `asdf set`은 `$HOME` 디렉터리에 있는 `.tool-versions` 파일에 기록하며, 해당 파일이 없을 경우 새로 생성합니다. `-p` / `--parent` 플래그를 사용하면 `asdf set`은 현재 디렉터리에서 가장 가까운 상위 디렉터리에 있는 `.tool-versions` 파일을 찾아 그 파일에 기록합니다. ### 환경 변수 사용 (Via Environment Variable) 버전을 결정할 때 `asdf`는 `ASDF_${TOOL}_VERSION` 형식의 환경 변수를 먼저 확인합니다. 버전 형식은 `.tool-versions` 파일에서 지원하는 형식과 동일합니다. 이 환경 변수가 설정되어 있으면, 어떤 `.tool-versions` 파일에 해당 도구의 버전이 설정되어 있더라도 **해당 값이 우선 적용**됩니다. 예를 들어: ```bash export ASDF_ELIXIR_VERSION=1.18.1 ``` 위 설정은 현재 셸 세션에서 `asdf`가 **Elixir 1.18.1**을 사용하도록 지정합니다. --- :::warning 대체 수단 이 설정은 **환경 변수**이기 때문에, **해당 변수가 설정된 위치(셸 세션)**에서만 적용됩니다. 이미 실행 중인 다른 셸 세션들은 `.tool-versions` 파일에 설정된 버전을 계속 사용합니다. 세부 내용은 Configuration 섹션의 `.tool-versions` [설정 섹션에 파일](/ko-kr/manage/configuration.md)을 참고하세요. ::: --- 다음 예시는 Elixir 프로젝트의 테스트를 **버전 1.4.0**으로 실행합니다: ```bash ASDF_ELIXIR_VERSION=1.4.0 mix test ``` ## 시스템 버전으로의 폴백 asdf 관리 버전이 아닌 `<name>` 도구의 시스템 버전을 사용하려면 도구의 버전을 `system`으로 설정할 수 있습니다. 위에 [현재 버전 설정](#현재-버전-설정) 섹션에 나와있는대로, `asdf set`이나 환경 변수를 사용하여 설정하세요. ```shell asdf set <name> system # asdf set python system ``` ## 현재 버전 보기 ```shell asdf current # asdf current # erlang 17.3 /Users/kim/.tool-versions # nodejs 6.11.5 /Users/kim/cool-node-project/.tool-versions asdf current <name> # asdf current erlang # erlang 17.3 /Users/kim/.tool-versions ``` ## 버전 제거 ```shell asdf uninstall <name> <version> # asdf uninstall erlang 17.3 ``` ## Shims asdf는 패키지를 설치할 때 해당 패키지의 모든 실행 프로그램에 대한 shim들을 `$ASDF_DATA_DIR/shims` 디렉토리 (기본값은 `~/.asdf/shims`)에 생성합니다. 이 디렉토리는 설치된 프로그램들이 이용가능하도록 `$PATH` (`asdf.sh`, `asdf.fish` 등)에 존재합니다. Shim 자체는 플러그인 이름과 shim이 감싸고 있는 설치된 패키지의 실행파일의 경로를 넘겨주는 `asdf exec`라는 헬퍼 프로그램을 `exec`시키는 매우 단순한 wrapper입니다. `asdf exec` 헬퍼는 사용할 패키지의 버전( `.tool-versions` 파일이나 환경 변수에 지정된 버전)을 결정하고, 패키지 설치 디렉터리 안에서 실행 파일의 최종 경로를 산출합니다 (이 경로는 플러그인의 `exec-path` 콜백을 통해 조정될 수 있습니다). 또한 실행에 사용할 환경을 결정하는데, 이 역시 플러그인이 제공하는 `exec-env` 스크립트를 통해 설정됩니다. 이 모든 과정이 끝나면, 해당 실행 파일을 실제로 실행합니다. ::: warning 노트 이 시스템은 `exec` 호출을 사용하기 때문에, 실행 대신 셸에 의해 source 되야하는 패키지의 스크립트는 shim wrapper를 통하지 않고 직접 액세스되야 합니다. 두 가지 `asdf` 명령어: `which`와 `where`는 설치된 패키지로의 경로를 반환할 수 있습니다: ::: ```shell # returns path to main executable in current version source $(asdf which ${PLUGIN})/../script.sh # returns path to the package installation directory source $(asdf where ${PLUGIN})/bin/script.sh ``` ### asdf shims 우회 어떠한 이유로 asdf의 shim들을 우회하고 싶거나 프로젝트의 디렉토리로 이동했을 때 자동으로 환경 변수를 설정되게 하고 싶으시면 [asdf-direnv](https://github.com/asdf-community/asdf-direnv) 플러그인이 도움이 될 것입니다. 상세한 내용은 README를 확인해 주세요. ================================================ FILE: docs/ko-kr/more/community-projects.md ================================================ # 커뮤니티 프로젝트 `asdf`와 관련된 커뮤니티 프로젝트들입니다: - [asdf-community](https://github.com/asdf-community): asdf 플로그인들의 장기 관리를 위한 커뮤니티 주도의 프로젝트입니다. - [asdf dev container](https://github.com/iloveitaly/asdf-devcontainer): GitHub 코드스페이스에서 asdf가 관리하는 툴들을 지원하는 [GitHub 개발 컨테이너](https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/introduction-to-dev-containers) ::: warning 노트 asdf 코어 팀은 이 프로젝트들 혹은 코드를 소유하지 않습니다. asdf 코어는 위의 목록들에 품질과 보안을 책임지지 않습니다. ::: ================================================ FILE: docs/ko-kr/more/faq.md ================================================ # 자주 묻는 질문 `asdf`에 관련된 공통된 질문들입니다. ## WSL1을 지원하나요? WSL1 ([Windows Subsystem for Linux](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux) 1)는 공식적으로 지원되지 않습니다. 어떤 부분의 `asdf`의 제대로 동작하지 않을 수 있습니다. 우리는 WSL1의 공식 지원을 추가할 계획이 없습니다. ## WSL2을 지원하나요? WSL2 ([Windows Subsystem for Linux](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux#WSL_2) 2)는 당신이 선택한 WSL distro를 위한 설치 & dependency 설명서를 따르면 작동합니다. 중요한 것은, WSL2는 _오직_ 현재 작업 디렉토리가 Unix 드라이브 그리고 Windows 드라이브에 종속되어 있지 않을때 정상적으로 동작합니다. 우리는 호스트 runner support가 GitHub Actions에서 사용가능할 때 WSL2에서 테스트 suite를 진행할 계획입니다만, 현재는 아직 이용가능하지 않은 것 같습니다. ## 새롭게 설치된 실행파일이 동작하지 않나요? > 방금 `npm install -g yarn`, 그러나 `yarn`을 실행시킬 수 없습니다. 어떻게 해야하나요? `asdf`는 [shims](<https://en.wikipedia.org/wiki/Shim_(computing)>)를 사용하여 실행파일들을 관리합니다. 플러그인에 의해서 설치되는 실행파일들은 자동적으로 shim이 생성되지만, `asdf`가 관리하고 있는 툴에 의해서 실행파일이 설치 된 경우는 shim을 생성해야 한다고 하는 것을 `asdf`에 알려줄 필요가 있습니다. 이러한 경우, [Yarn](https://yarnpkg.com/)의 shim을 생성하기 위해 [`asdf reshim` 명령어 문서](/ko-kr/manage/core.md#Shim-재생성)를 참고하세요. ## 셸이 새롭게 설치된 shims들을 감지하지 못하나요? 만약 `asdf reshim`가 문제를 해결하지 못한다면, 대부분의 경우 `asdf.sh` 혹은 `asdf.fish` sourcing이 당신의 셸 설정 파일 (`.bash_profile`, `.zshrc`, `config.fish` etc) **아래쪽에** 있지 _않을_ 가능성이 높습니다. 당신의 `$PATH`가 설정 된 **후에** 그리고 사용중인 프레임워크 (oh-my-zsh etc)가 source 된 **후에** source 되어야 합니다. ================================================ FILE: docs/ko-kr/more/thanks.md ================================================ # 감사인사 asdf 저자들 & 기여자들을 위한 감사 페이지! ## Credits 나 ([@HashNuke](https://github.com/HashNuke)), 고열, 감기, 기침. 2014년부터 종료까지의 저작권 ([MIT License](https://github.com/asdf-vm/asdf/blob/master/LICENSE)) ## 관리자 - [@HashNuke](https://github.com/HashNuke) - [@danhper](https://github.com/danhper) - [@Stratus3D](https://github.com/Stratus3D) - [@vic](https://github.com/vic) - [@jthegedus](https://github.com/jthegedus) ## 기여자 GitHub에서 [기여자들의 목록](https://github.com/asdf-vm/asdf/graphs/contributors)를 확인하세요 :pray: ================================================ FILE: docs/ko-kr/plugins/create.md ================================================ # 플러그인 생성하기 플러그인은 언어 / 툴의 버전 관리를 지원하는 실행 가능한 스크립트들이 있는 Git 리포지토리입니다. 이 스크립트들은 asdf에 의해 특정 명령어들을 받아 `asdf list-all <name>`, `asdf install <name> <version>` 등의 지원을 위해 실행됩니다. ## 빠른 시작 자체 플러그인을 만드는 것을 시작하는 두 가지 옵션이 있습니다: 1. [asdf-vm/asdf-plugin-template](https://github.com/asdf-vm/asdf-plugin-template) 리포지토리 사용해서 기본 스크립트가 구현된 (`asdf-<tool_name>` 이름으로) 플러그인 리포지토리 [생성하기](https://github.com/asdf-vm/asdf-plugin-template/generate). 리포지토리가 생성되면, 그 리포지토리를 clone하고 템플릿을 유기적으로 업데이트하여 `setup.bash` 스크립트를 실행합니다. 2. `asdf-<tool_name>`로 이룸 붙인 리포지토리를 시작하고 아래 문서에 필수 스크립트들을 구현하세요. ### 플리그인 스크립트들을 위한 황금률 - 스크립트는 다른 `asdf` 명령어를 호출하면 **안됩니다**. - 셸 툴/명령어의 dependency를 최소로 유지하세요. - non-portable 툴이나 명령어 플래그의 사용을 피하세요. 예를 들어, `sort -V`. asdf core를 참고하세요 [금지된 명령어 목록](https://github.com/asdf-vm/asdf/blob/master/test/banned_commands.bats) ## 스크립트 개요 asdf에서 호출 가능한 스크립트의 전체 목록입니다. | 스크립트 | 설명 | | :--------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------- | | [bin/list-all](#bin-list-all) <Badge type="tip" text="필수" vertical="middle" /> | 모든 설치 가능한 버전들을 나열 | | [bin/download](#bin-download) <Badge type="tip" text="필수" vertical="middle" /> | 지정한 버전에 대한 소스 코드 또는 바이너리 다운로드 | | [bin/install](#bin-install) <Badge type="tip" text="필수" vertical="middle" /> | 지정된 버전을 설치 | | [bin/latest-stable](#bin-latest-stable) <Badge type="warning" text="추천" vertical="middle" /> | 지정된 도구의 최신 안정 버전 나열 | | [bin/help.overview](#bin-help.overview) | 플러그인 및 도구에 대한 일반적인 설명을 출력 | | [bin/help.deps](#bin-help.deps) | 운영 체제별 dependencies 목록 출력 | | [bin/help.config](#bin-help.config) | 플러그인 및 툴 설정 정보 출력 | | [bin/help.links](#bin-help.links) | 플러그인 및 툴에 대한 링크 목록 출력 | | [bin/list-bin-paths](#bin-list-bin-paths) | shim들을 생성하기 위해 바이너리 파일이 있는 디렉토리에 대한 상대 경로 나열 | | [bin/exec-env](#bin-exec-env) | 바이너리 실행을 위한 환경 준비 | | [bin/exec-path](#bin-exec-path) | 툴 버전의 실행파일 경로 출력 | | [bin/uninstall](#bin-uninstall) | 툴의 특정 버전 제거 | | [bin/list-legacy-filenames](#bin-list-legacy-filenames) | 레거시 버전 파일의 이름 출력: `.ruby-version` | | [bin/parse-legacy-file](#bin-parse-legacy-file) | 레거시 버전 파일들을 위한 맞춤 parser | | [bin/post-plugin-add](#bin-post-plugin-add) | 플러그인이 추가된 후 실행될 훅 | | [bin/post-plugin-update](#bin-post-plugin-update) | 플러그인이 업데이트 된 후 실행될 훅 | | [bin/pre-plugin-remove](#bin-pre-plugin-remove) | 플러그인이 제거되기 전 실행될 훅 | 어떤 명령어가 어떤 스크립트를 호출하는지 확인하려면, 각 스크립트에 대한 자세한 문서를 참조하세요. ## 환경 변수 개요 모든 스크립트에서 사용되는 환경 변수의 전체 목록입니다. | 환경 변수 | 설명 | | :----------------------- | :---------------------------------------------------------------- | | `ASDF_INSTALL_TYPE` | `version` 또는 `ref` | | `ASDF_INSTALL_VERSION` | `ASDF_INSTALL_TYPE`에 따른 풀 버전 번호 또는 Git Ref | | `ASDF_INSTALL_PATH` | 툴이 설치 _되어야하는_ 혹은 _되어있는_ 경로 | | `ASDF_CONCURRENCY` | 소스 코드를 컴파일할 때 사용할 코어 수. `make-j`를 설정할 때 유용 | | `ASDF_DOWNLOAD_PATH` | `bin/download`에 의해 소스 코드 또는 바이너리가 다운로드 된 경로 | | `ASDF_PLUGIN_PATH` | 플러그인이 설치된 경로 | | `ASDF_PLUGIN_SOURCE_URL` | 플러그인의 소스 URL | | `ASDF_PLUGIN_PREV_REF` | 플러그인 리포지토리의 이전 `git-ref` | | `ASDF_PLUGIN_POST_REF` | 플러그인 리포지토리의 업데이트 된 `git-ref` | | `ASDF_CMD_FILE` | source 되는 파일의 전체 경로를 해결 | ::: tip 노트 **모든 스크립트에서 모든 환경 변수를 사용할 수 있는 것은 아닙니다.** 아래 각 스크립트에 대한 문서를 확인하여 사용할 수 있는 환경 변수들을 확인하세요. ::: ## 필수적 스크립트 ### `bin/list-all` <Badge type="tip" text="필수" vertical="middle" /> **설명** 설치 가능한 모든 버전 나열. **출력 형식** **공백으로 구분된** 문자열을 반드시 출력. 예를 들어: ```txt 1.0.1 1.0.2 1.3.0 1.4 ``` 최신 버전이 마지막에 와야 합니다. asdf core는 각 버전을 각각의 행에 출력하여, 일부 버전을 화면 밖으로 밀어낼 가능성이 있습니다. **정렬** 웹사이트의 릴리스 페이지에서 버전을 가져오는 경우에는 이미 올바른 순서로 되어 있는 경우가 많기 때문에 제공된 순서대로 두는 것이 좋습니다. 역순으로 되어 있는 경우 `tac`을 통해 해당 버전들을 바로 잡는것으로 충분합니다. 정렬이 불가피한 경우, `sort -V`는 사용이 불가능하므로, 다음 중 하나를 제안합니다: - [Git 정렬 기능 사용](https://github.com/asdf-vm/asdf-plugin-template/blob/main/template/lib/utils.bash) (Git `v2.18.0` 이상 필요) - [맞춤 정렬 함수 작성](https://github.com/vic/asdf-idris/blob/master/bin/list-all#L6) (`sed`, `sort` & `awk` 필요) **스크립트에서 사용 가능한 환경 변수** 이 스크립트에는 환경 변수가 제공되지 않습니다. **이 스크립트를 호출하는 명령어** - `asdf list all <name> [version]` - `asdf list all nodejs`: 이 스크립트에 의해 반환된 모든 버전을 나열합니다, 한 행에 한개씩. - `asdf list all nodejs 18`: 이 스크립트에 의해 반환된 모든 버전을 나열하며, 각 행에 하나씩, `18`로 시작하는 모든 버전에 필터가 적용됩니다. **asdf core에서 호출 시그니처** 제공되는 매개변수는 없습니다. ```bash "${plugin_path}/bin/list-all" ``` --- ### `bin/download` <Badge type="tip" text="필수" vertical="middle" /> **설명** 지정된 장소에 지정된 버전에 대한 소스 코드 또는 바이너리 다운로드 **구현 세부사항** - 스크립트는 소스 또는 바이너리를 `ASDF_DOWNLOAD_PATH`에서 지정된 디렉토리에 다운로드해야합니다. - 압축 해제된 소스 코드 또는 바이너리만 `ASDF_DOWNLOAD_PATH` 디렉토리에 위치해야합니다. - 실패 시에는 `ASDF_DOLOAD_PATH`에 어떠한 파일도 남아서는 안 됩니다. - 성공 시에는 `0`이 종료 코드입니다. - 실패 시에는 0이 아닌 상태의 종료 코드입니다. **레거시 플러그인** 비록 이 스크립트는 모든 플러그인에서 *필수*로 되어 있지만, 이 스크립트가 도입되기 이전의 "레거시" 플러그인에서는 _선택_ 입니다. 이 스크립트가 없는 경우, asdf는 `bin/install` 스크립트가 있다고 가정하고 해당 버전을 다운로드 **그리고** 설치합니다. 레거시 플러그인 지원은 최종적으로 제거될 예정이기 때문에 앞으로 작성할 모든 플러그인에서 이 스크립트를 포함해야합니다. **스크립트에서 사용 가능한 환경 변수** - `ASDF_INSTALL_TYPE`: `version` 또는 `ref` - `ASDF_INSTALL_VERSION`: - `ASDF_INSTALL_TYPE=version`의 경우, 풀 버전 번호. - `ASDF_INSTALL_TYPE=ref`의 경우, Git ref (태그/커밋/브랜치). - `ASDF_INSTALL_PATH`: 툴이 설치 _되어있는_, 또는 _되어야하는_ 경로. - `ASDF_DOWNLOAD_PATH`: 소스 코드 또는 바이너리 파일이 다운로드 된 경로. **이 스크립트를 호출하는 명령어** - `asdf install <tool> [version]` - `asdf install <tool> latest[:version]` - `asdf install nodejs 18.0.0`: Node.js 버전 `18.0.0`의 소스 코드 또는 바이너리를 다운로드하고 `ASDF_DOWNLOAD_PATH` 디렉토리에 저장. 그 다음 `bin/install` 스크립트를 실행. **asdf core에서 호출 시그니처** 제공되는 매개변수는 없습니다. ```bash "${plugin_path}"/bin/download ``` --- ### `bin/install` <Badge type="tip" text="필수" vertical="middle" /> **설명** 특정 버전의 도구를 지정된 위치에 설치. **구현 세부사항** - 스크립트는 `ASDF_INSTALL_PATH` 경로에 지정된 버전을 설치해야합니다. - Shim은 `$ASDF_INSTALL_PATH/bin`에 있는 어떠한 파일에 대해서든 기본적으로 생성됩니다. 이 동작은 선택적 [bin/list-bin-paths](#binlist-bin-paths) 스크립트로 맞춤 설정 가능합니다. - 성공 시에는 `0`이 종료 코드입니다. - 실패 시에는 0이 아닌 상태의 종료 코드입니다. - TOCTOU(Time-of-Check-to-Off-Use) 문제를 방지하려면, 툴의 빌드 및 설치가 성공적이라고 판단될때만 스크립트에서 파일을 `ASDF_INSTALL_PATH`에 배치합니다. **레거시 플러그인** `bin/download` 스크립트가 없는 경우, 이 스크립트는 지정된 버전을 다운로드 **그리고** 설치해야합니다. `0.7._`보다 이전 그리고 `0.8._`보다 이후 asdf 코어 버전들의 호환성을 확인하려면, `ASDF_DOWNLOAD_PATH` 환경 변수가 있는지 확인합니다. 그 환경 변수가 존재하는 경우, 이미 `bin/download` 스크립트가 그 버전을 다운로드했다고 가정하고, 존재하지 않으면 `bin/install` 스크립트에서 소스 코드를 다운로드합니다. **스크립트에서 사용 가능한 환경 변수** - `ASDF_INSTALL_TYPE`: `version` 또는 `ref` - `ASDF_INSTALL_VERSION`: - `ASDF_INSTALL_TYPE=version`의 경우, 풀 버전 번호. - `ASDF_INSTALL_TYPE=ref`의 경우, Git ref (태그/커밋/브랜치). - `ASDF_INSTALL_PATH`: 툴이 설치 _되어있는_, 또는 _되어야하는_ 경로. - `ASDF_CONCURRENCY`: 소스 코드를 컴파일할 때 사용할 코어 수. `make-j`를 설정할 때 유용. - `ASDF_DOWNLOAD_PATH`: 소스 코드 또는 바이너리 파일이 다운로드 된 경로. **이 스크립트를 호출하는 명령어** - `asdf install` - `asdf install <tool>` - `asdf install <tool> [version]` - `asdf install <tool> latest[:version]` - `asdf install nodejs 18.0.0`: Node.js 버전 `18.0.0`을 `ASDF_INSTALL_PATH` 디렉토리에 설치. **asdf core에서 호출 시그니처** 제공되는 매개변수는 없습니다. ```bash "${plugin_path}"/bin/install ``` ## 선택적 스크립트 ### `bin/latest-stable` <Badge type="warning" text="추천" vertical="middle" /> **설명** 도구의 최신 안정 버전을 결정합니다. 이 스크립트가 존재하지 않는 경우, asdf 코어는 `bin/list-all`의 출력을 비의도적으로 `tail`합니다. **구현 세부사항** - 스크립트는 도구의 최신 안정 버전을 표준 출력에 출력해야합니다. - 비안정판이나 릴리스 후보판은 제외되어야 합니다. - 필터 쿼리는 스크립트의 첫 번째 인수로 제공됩니다 이 쿼리는 버전 번호나 툴 제공자에 의한 출력값을 필터하기 위해 사용되어야 합니다. - 예를 들어 [ruby 플러그인](https://github.com/asdf-vm/asdf-ruby)에서의 `asdf list all ruby`는 `jruby`, `rbx`, `truffleruby` 등의 많은 제공자들의 Ruby 버전 목록을 출력합니다. 사용자가 제공한 필터는 플러그인이 유의적 버전 및/또는 공급자를 필터링하는 데 사용될 수 있습니다. ``` > asdf latest ruby 3.2.2 > asdf latest ruby 2 2.7.8 > asdf latest ruby truffleruby truffleruby+graalvm-22.3.1 ``` - 성공 시에는 `0`이 종료 코드입니다. - 실패 시에는 0이 아닌 상태의 종료 코드입니다. **스크립트에서 사용 가능한 환경 변수** - `ASDF_INSTALL_TYPE`: `version` 또는 `ref` - `ASDF_INSTALL_VERSION`: - `ASDF_INSTALL_TYPE=version`의 경우, 풀 버전 번호. - `ASDF_INSTALL_TYPE=ref`의 경우, Git ref (태그/커밋/브랜치). - `ASDF_INSTALL_PATH`: 툴이 설치 _되어있는_, 또는 _되어야하는_ 경로. **이 스크립트를 호출하는 명령어** - `asdf set <tool> latest`: 툴의 버전을 해당 툴의 최신 안정 버전으로 설정합니다. - `asdf install <tool> latest`: 최신 버전의 툴을 설치합니다. - `asdf latest <tool> [<version>]`: 선택적인 필터를 기반으로 도구의 최신 버전을 출력합니다. - `asdf latest --all`: asdf에서 관리하는 모든 툴의 최신 버전과 설치 여부를 출력합니다. **asdf core에서 호출 시그니처** 이 스크립트는 필터 쿼리라는 하나의 인수를 받습니다. ```bash "${plugin_path}"/bin/latest-stable "$query" ``` --- ### `bin/help.overview` **설명** 플러그인 및 관리 중인 툴에 대한 일반적인 설명을 출력. **구현 세부사항** - 플러그인에 대한 도움말 출력을 표시하려면 이 스크립트가 필요합니다. - asdf 코어가 머리말를 인쇄하므로 머리말을 출력해서는 안 됩니다. - 자유로운 형식의 텍스트로 출력해도 상관없지만 짧은 한 단락 정도의 설명이 이상적입니다. - 핵심이 되는 asdf-vm 문서에서 이미 설명되어 있는 정보는 출력하지 않아야 합니다. - 운영 체제와 설치된 툴의 버전에 맞게 출력해야합니다 (필요에 따라 `ASDF_INSTALL_VERSION` 및 `ASDF_INSTALL_TYPE` 환경 변수의 값을 사용하십시오). - 성공 시에는 `0`이 종료 코드입니다. - 실패 시에는 0이 아닌 상태의 종료 코드입니다. **스크립트에서 사용 가능한 환경 변수** - `ASDF_INSTALL_TYPE`: `version` 또는 `ref` - `ASDF_INSTALL_VERSION`: - `ASDF_INSTALL_TYPE=version`의 경우, 풀 버전 번호. - `ASDF_INSTALL_TYPE=ref`의 경우, Git ref (태그/커밋/브랜치). - `ASDF_INSTALL_PATH`: 툴이 설치 _되어있는_, 또는 _되어야하는_ 경로. **이 스크립트를 호출하는 명령어** - `asdf help <name> [<version>]`: 플러그인 및 도구 문서를 출력 **asdf core에서 호출 시그니처** ```bash "${plugin_path}"/bin/help.overview ``` --- ### `bin/help.deps` **설명** 운영 체제에 맞는 dependencies 목록을 출합니다. 한 행마다 한 개의 dependency. ```bash git curl sed ``` **구현 세부사항** - 이 스크립트의 출력되기 위해서는 `bin/help.overview`가 필요합니다. - 운영 체제와 설치된 툴의 버전에 맞게 출력해야합니다 (필요에 따라 `ASDF_INSTALL_VERSION` 및 `ASDF_INSTALL_TYPE` 환경 변수의 값을 사용하십시오). - 성공 시에는 `0`이 종료 코드입니다. - 실패 시에는 0이 아닌 상태의 종료 코드입니다. **스크립트에서 사용 가능한 환경 변수** - `ASDF_INSTALL_TYPE`: `version` 또는 `ref` - `ASDF_INSTALL_VERSION`: - `ASDF_INSTALL_TYPE=version`의 경우, 풀 버전 번호. - `ASDF_INSTALL_TYPE=ref`의 경우, Git ref (태그/커밋/브랜치). - `ASDF_INSTALL_PATH`: 툴이 설치 _되어있는_, 또는 _되어야하는_ 경로. **이 스크립트를 호출하는 명령어** - `asdf help <name> [<version>]`: 플러그인 및 도구 문서를 출력 **asdf core에서 호출 시그니처** ```bash "${plugin_path}"/bin/help.deps ``` --- ### `bin/help.config` **설명** 플러그인 및 도구에 필수적 또는 선택적 설정 출력. 예를 들어, 도구를 설치하거나 컴파일하는 데 필요한 환경 변수나 기타 플래그를 설명. **구현 세부사항** - 이 스크립트의 출력되기 위해서는 `bin/help.overview`가 필요합니다. - 자유로운 형식의 텍스트로 출력할 수 있습니다. - 운영 체제와 설치된 툴의 버전에 맞게 출력해야합니다 (필요에 따라 `ASDF_INSTALL_VERSION` 및 `ASDF_INSTALL_TYPE` 환경 변수의 값을 사용하십시오). - 성공 시에는 `0`이 종료 코드입니다. - 실패 시에는 0이 아닌 상태의 종료 코드입니다. **스크립트에서 사용 가능한 환경 변수** - `ASDF_INSTALL_TYPE`: `version` 또는 `ref` - `ASDF_INSTALL_VERSION`: - `ASDF_INSTALL_TYPE=version`의 경우, 풀 버전 번호. - `ASDF_INSTALL_TYPE=ref`의 경우, Git ref (태그/커밋/브랜치). - `ASDF_INSTALL_PATH`: 툴이 설치 _되어있는_, 또는 _되어야하는_ 경로. **이 스크립트를 호출하는 명령어** - `asdf help <name> [<version>]`: 플러그인 및 도구 문서를 출력 **asdf core에서 호출 시그니처** ```bash "${plugin_path}"/bin/help.config ``` --- ### `bin/help.links` **설명** 플러그인 및 툴과 관련된 링크 목록을 출력. 한 행마다 한 개의 링크. ```bash Git Repository: https://github.com/vlang/v Documentation: https://vlang.io ``` **구현 세부사항** - 이 스크립트의 출력되기 위해서는 `bin/help.overview`가 필요합니다. - 한행마다 한 개의 링크. - 형식은 다음 중에 하나여야합니다: - `<title>: <link>` - 또는 그냥 `<link>` - 운영 체제와 설치된 툴의 버전에 맞게 출력해야합니다 (필요에 따라 `ASDF_INSTALL_VERSION` 및 `ASDF_INSTALL_TYPE` 환경 변수의 값을 사용하십시오). - 성공 시에는 `0`이 종료 코드입니다. - 실패 시에는 0이 아닌 상태의 종료 코드입니다. **스크립트에서 사용 가능한 환경 변수** - `ASDF_INSTALL_TYPE`: `version` 또는 `ref` - `ASDF_INSTALL_VERSION`: - `ASDF_INSTALL_TYPE=version`의 경우, 풀 버전 번호. - `ASDF_INSTALL_TYPE=ref`의 경우, Git ref (태그/커밋/브랜치). - `ASDF_INSTALL_PATH`: 툴이 설치 _되어있는_, 또는 _되어야하는_ 경로. **이 스크립트를 호출하는 명령어** - `asdf help <name> [<version>]`: 플러그인 및 도구 문서를 출력 **asdf core에서 호출 시그니처** ```bash "${plugin_path}"/bin/help.links ``` --- ### `bin/list-bin-paths` **설명** 툴의 특정 버전에서 실행파일이 포함된 디렉토리 목록을 출력. **구현 세부사항** - 이 스크립트가 존재하지 않는 경우, asdf는 `"${ASDF_INSTALL_PATH}"/bin` 디렉토리 내에 있는 바이너리들을 찾아 그 바이너리를 위한 shim들을 생성합니다. - 실행파일이 포함된 디렉토리의 경로를 공백으로 구분하여 출력합니다. - 경로는 `ASDF_INSTALL_PATH`로의 상대 경로이어야 합니다. 출력 예시는 다음과 같습니다: ```bash bin tools veggies ``` 이는 asdf가 그 파일들을 위한 shim들을 다음 위치에 생성하게 지시합니다: - `"${ASDF_INSTALL_PATH}"/bin` - `"${ASDF_INSTALL_PATH}"/tools` - `"${ASDF_INSTALL_PATH}"/veggies` **스크립트에서 사용 가능한 환경 변수** - `ASDF_INSTALL_TYPE`: `version` 또는 `ref` - `ASDF_INSTALL_VERSION`: - `ASDF_INSTALL_TYPE=version`의 경우, 풀 버전 번호. - `ASDF_INSTALL_TYPE=ref`의 경우, Git ref (태그/커밋/브랜치). - `ASDF_INSTALL_PATH`: 툴이 설치 _되어있는_, 또는 _되어야하는_ 경로. **이 스크립트를 호출하는 명령어** - `asdf install <tool> [version]`: 초기에 바이너리들을 위한 shim들 생성. - `asdf reshim <tool> <version>`: 바이너리들을 위한 shim들 재생성. **asdf core에서 호출 시그니처** ```bash "${plugin_path}/bin/list-bin-paths" ``` --- ### `bin/exec-env` **설명** 툴 바이너리의 shim을 실행하기 전에 환경을 준비. **스크립트에서 사용 가능한 환경 변수** - `ASDF_INSTALL_TYPE`: `version` 또는 `ref` - `ASDF_INSTALL_VERSION`: - `ASDF_INSTALL_TYPE=version`의 경우, 풀 버전 번호. - `ASDF_INSTALL_TYPE=ref`의 경우, Git ref (태그/커밋/브랜치). - `ASDF_INSTALL_PATH`: 툴이 설치 _되어있는_, 또는 _되어야하는_ 경로. **이 스크립트를 호출하는 명령어** - `asdf which <command>`: 실행파일의 경로 표시 - `asdf exec <command> [args...]`: 현재 버전에서 shim 명령을 실행 - `asdf env <command> [util]`: shim 명령어 실행 시 사용되는 환경에서 유틸리티(기본값: `env`)를 실행. **asdf core에서 호출 시그니처** ```bash "${plugin_path}/bin/exec-env" ``` --- ### `bin/exec-path` 툴의 특정 버전의 실행파일 경로를 가져옵니다. 실행파일에 대한 상대 경로를 문자열로 출력해야합니다. 이는 플러그인이 shim에서 지정한 실행파일 경로를 조건부로 덮어쓰게 하거나, 그렇지 않으면 shim에서 지정한 기본 경로를 반환합니다. **설명** 툴의 특정 버전의 실행파일 경로를 가져옵니다. **구현 세부사항** - 실행파일에 대한 상대 경로를 문자열로 출력. - Shim에서 지정한 실행파일 경로를 조건부로 덮어쓰거나, 그렇지 않으면 shim에서 지정한 기본 경로를 반환. ```shell Usage: plugin/bin/exec-path <install-path> <command> <executable-path> Example Call: ~/.asdf/plugins/foo/bin/exec-path "~/.asdf/installs/foo/1.0" "foo" "bin/foo" Output: bin/foox ``` **스크립트에서 사용 가능한 환경 변수** - `ASDF_INSTALL_TYPE`: `version` 또는 `ref` - `ASDF_INSTALL_VERSION`: - `ASDF_INSTALL_TYPE=version`의 경우, 풀 버전 번호. - `ASDF_INSTALL_TYPE=ref`의 경우, Git ref (태그/커밋/브랜치). - `ASDF_INSTALL_PATH`: 툴이 설치 _되어있는_, 또는 _되어야하는_ 경로. **이 스크립트를 호출하는 명령어** - `asdf which <command>`: 실행파일의 경로 표시 - `asdf exec <command> [args...]`: 현재 버전에서 shim 명령을 실행 - `asdf env <command> [util]`: shim 명령어 실행 시 사용되는 환경에서 유틸리티(기본값: `env`)를 실행. **asdf core에서 호출 시그니처** ```bash "${plugin_path}/bin/exec-path" "$install_path" "$cmd" "$relative_path" ``` --- ### `bin/uninstall` **설명** 툴의 지정된 버전을 제거합니다. **출력 형식** 출력값은 `stdout` 또는 `stderr`에 적절히 송신 되어야 합니다. 어떠한 출력값도 후속 코어 실행에 의해 사용되지 않습니다. **스크립트에서 사용 가능한 환경 변수** 이 스크립트에는 환경 변수가 제공되지 않습니다. **이 스크립트를 호출하는 명령어** - `asdf list all <name> <version>` - `asdf uninstall nodejs 18.15.0`: nodejs의 `18.15.0` 버전을 제거, `npm i -g`로 설치된 모든 글로벌 shim들 또한 제거. **asdf core에서 호출 시그니처** 제공되는 매개변수는 없습니다. ```bash "${plugin_path}/bin/uninstall" ``` --- ### `bin/list-legacy-filenames` **설명** 툴 버전을 결정하는 데 사용된 레거시 설정 파일 목록을 출력. **구현 세부사항** - 파일이름들의 목록을 공백으로 구분하여 출력. ```bash .ruby-version .rvmrc ``` - `"${HOME}"/.asdfrc`에서 `legacy_version_file` 옵션을 활성화한 사용자에게만 적용됩니다. **스크립트에서 사용 가능한 환경 변수** - `ASDF_INSTALL_TYPE`: `version` 또는 `ref` - `ASDF_INSTALL_VERSION`: - `ASDF_INSTALL_TYPE=version`의 경우, 풀 버전 번호. - `ASDF_INSTALL_TYPE=ref`의 경우, Git ref (태그/커밋/브랜치). - `ASDF_INSTALL_PATH`: 툴이 설치 _되어있는_, 또는 _되어야하는_ 경로. **이 스크립트를 호출하는 명령어** 툴 버전을 가져오는 모든 명령에서 호출됩니다. **asdf core에서 호출 시그니처** 제공되는 매개변수는 없습니다. ```bash "${plugin_path}/bin/list-legacy-filenames" ``` --- ### `bin/parse-legacy-file` **설명** asdf에 의해 발견된 레거시 파일을 parse하여 툴의 버전을 결정. 자바스크립트의 `package.json`이나 Go 언어의 `go.mod`와 같은 파일에서 버전 번호를 추출하는 데 유용. **구현 세부사항** - 이 스크립트가 존재하지 않는 경우, asdf는 단순히 레거시 파일을 `cat`하여 버전을 결정합니다. - 다음과 같은 상황에서도 **결정론적**이고 항상 동일하고 정확한 버전을 반환해야합니다: - 동일한 레거시 파일을 구문 parsing할 때. - 무엇이 설치되어 있는지 또는 레거시 버전이 유효하거나 완전한지는 관계 없이. 일부 레거시 파일 형식은 맞지 않을 수도 있습니다. - 아래와 같이 버전 번호를 한 줄로 출력해 주세요: ```bash 1.2.3 ``` **스크립트에서 사용 가능한 환경 변수** 이 스크립트가 호출되기 전에 환경 변수가 설정되지 않습니다. **이 스크립트를 호출하는 명령어** 툴 버전을 가져오는 모든 명령에서 호출됩니다. **asdf core에서 호출 시그니처** 이 스크립트는 레거시 파일의 내용을 읽기 위해 레거시 파일의 경로라는 하나의 인수를 받습니다. ```bash "${plugin_path}/bin/parse-legacy-file" "$file_path" ``` --- ### `bin/post-plugin-add` **설명** 이 콜백 스크립트는 asdf의 `asdf plugin add <tool>` 명령어로 플러그인이 _추가된_ **후에** 실행됩니다. 관련된 명령어 훅들을 참조하세요: - `pre_asdf_plugin_add` - `pre_asdf_plugin_add_${plugin_name}` - `post_asdf_plugin_add` - `post_asdf_plugin_add_${plugin_name}` **스크립트에서 사용 가능한 환경 변수** - `ASDF_PLUGIN_PATH`: 플러그인이 설치된 경로. - `ASDF_PLUGIN_SOURCE_URL`: 플러그인 소스의 URL. 로컬 디렉토리 경로일 수 있음. **asdf core에서 호출 시그니처** 제공되는 매개변수는 없습니다. ```bash "${plugin_path}/bin/post-plugin-add" ``` --- ### `bin/post-plugin-update` **설명** 이 콜백 스크립트는 asdf가 `asdf plugin update <tool> [<git-ref>]` 커맨드로 플러그인 _업데이트_ 를 다운로드한 **후에** 실행됩니다. 관련된 명령어 훅들을 참조하세요: - `pre_asdf_plugin_update` - `pre_asdf_plugin_update_${plugin_name}` - `post_asdf_plugin_update` - `post_asdf_plugin_update_${plugin_name}` **스크립트에서 사용 가능한 환경 변수** - `ASDF_PLUGIN_PATH`: 플러그인이 설치된 경로. - `ASDF_PLUGIN_PREV_REF`: 플러그인의 이전 git-ref - `ASDF_PLUGIN_POST_REF`: 플러그인의 업데이트 된 git-ref **asdf core에서 호출 시그니처** 제공되는 매개변수는 없습니다. ```bash "${plugin_path}/bin/post-plugin-update" ``` --- ### `bin/pre-plugin-remove` **설명** asdf가 `asdf plugin remove <tool>` 커맨드로 플러그인을 제거하기 **전에** 이 콜백 스크립트를 실행시키세요. 관련된 명령어 훅들을 참조하세요: - `pre_asdf_plugin_remove` - `pre_asdf_plugin_remove_${plugin_name}` - `post_asdf_plugin_remove` - `post_asdf_plugin_remove_${plugin_name}` **스크립트에서 사용 가능한 환경 변수** - `ASDF_PLUGIN_PATH`: 플러그인이 설치된 경로. **asdf core에서 호출 시그니처** 제공되는 매개변수는 없습니다. ```bash "${plugin_path}/bin/pre-plugin-remove" ``` <!-- TODO: document command hooks --> <!-- ## Command Hooks --> ## asdf CLI 확장 명령어 <Badge type="danger" text="고급" vertical="middle" /> `lib/commands/command*.bash` 스크립트 또는 플러그인 이름을 하위명령어로 사용하여 asdf 명령줄 인터페이스를 통해 호출할 수 있는 실행파일을 제공함으로써 새로운 asdf 명령어를 정의할 수 있습니다. 예를 들면, `foo`라고 하는 플러그인이 있다고 하면: ```shell foo/ lib/commands/ command.bash command-bat.bash command-bat-man.bash command-help.bash ``` 사용자는 아래 명령을 실행할 수 있게 됩니다: ```shell $ 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` $ asdf foo bat man # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-bat-man.bash` $ asdf foo bat baz # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-bat.bash baz` ``` 플러그인 개발자는 이 기능을 사용하여 툴과 관련된 유틸리티를 제공하거나, asdf 자체의 명령어 확장 플러그인을 생성할 수 있습니다. 실행 가능 비트(executable bit)가 부여되어 있는 경우, asdf 실행을 대신하여 해당 스크립트가 실행됩니다. 실행 가능 비트(executable bit)가 부여되지 않은 경우, asdf는 해당 스크립트를 Bash 스크립트로 source합니다. `$ASDF_CMD_FILE`는 source 되는 파일의 전체 경로를 해결합니다. [`haxe`](https://github.com/asdf-community/asdf-haxe)는 이 기능을 사용하는 플러그인의 좋은 예시입니다. 이 플러그인은 Haxe 실행파일이 해당 디렉토리에서 상대적으로 동적 라이브러리를 찾으려하는 문제해결을 위해 `asdf haxe neko-dylibs-link`를 제공합니다. 플러그인 README에는 asdf 확장 명령어에 관한 것을 반드시 기재하도록 하십시오. ## 맞춤 Shim 템플릿 <Badge type="danger" text="고급" vertical="middle" /> ::: warning 경고 **반드시** 필요한 경우에만 사용하세요. ::: asdf에서는 맞춤 shim 템플릿을 사용할 수 있습니다. `foo`라고 하는 실행파일에 대해, 플러그인 내에 `shims/foo` 파일이 존재하면, asdf는 표준 shim 템플릿을 사용하는 대신 그 파일을 복사합니다. **이 기능은 현명하게 사용해야합니다.** asdf 코어팀이 파악하고 있는 것은, 이 기능은 오직 공식 플러그인 [Elixir 플러그인](https://github.com/asdf-vm/asdf-elixir)에서만 사용되고 있습니다. 이는 실행파일은 실행파일일 뿐만 아니라 Elixir 파일로도 읽히기 때문입니다. 이 때문에 표준 Bash shim을 사용할 수 없습니다. ## 테스팅 `asdf`에는 플러그인을 테스트하기 위한 `plugin-test` 명령어가 포함되어 있습니다: ```shell asdf plugin test <plugin_name> <plugin_url> [--asdf-tool-version <version>] [--asdf-plugin-gitref <git_ref>] [test_command...] ``` - `<plugin_name>` 및 `<plugin_url>`는 필수적입니다 - 옵션에서 `[--asdf-tool-version <version>]`를 지정하면, 해당 지정된 버전의 툴이 설치됩니다. 기본값은 `asdf latest <plugin-name>`입니다. - 옵션에서 `[--asdf-plugin-gitref <git_ref>]`를 지정하면, 그 커밋/브랜치/태그로 플러그인 자체를 체크아웃합니다. 이것은 플러그인 CI에서 풀 요청을 테스트할 때 유용합니다. - 선택적 매개변수 `[test_command...]`는 설치된 툴이 올바르게 동작하는지 확인하기위해 실행시키는 명령어입니다. 일반적으로 `<tool> --version` 또는 `<tool> --help`입니다. 예를 들어, NodeJS 플러그인을 테스트하기 위해, 다음을 실행시킬 수 있습니다 ```shell # asdf plugin test <plugin_name> <plugin_url> [test_command] asdf plugin test nodejs https://github.com/asdf-vm/asdf-nodejs.git node --version ``` ::: tip 노트 리눅스와 맥 운영체제 양쪽 CI 환경에서 모두 테스트하는 것을 권장합니다. ::: ### GitHub Action [asdf-vm/actions](https://github.com/asdf-vm/actions) 리포지토리는 GitHub에서 호스팅되는 플러그인을 테스트하기 위한 GitHub Action을 제공합니다. `.github/workflows/test.yaml` 액션 워크플로우 예시: ```yaml 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" ``` ### TravisCI 설정 `.travis.yml` 예시 파일, 필요에 따라 바꿔 사용하세요: ```yaml language: c 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 os: - linux - osx ``` ::: tip 노트 다른 CI를 사용하는 경우, 플러그인 위치에 대한 상대 경로를 전달할 필요가 있는 경우가 있습니다: ```shell asdf plugin test <tool_name> <path> '<tool_command> --version' ``` ::: ## API 속도 제한 `bin/list-all`이나 `bin/latest-stable`과 같이 명령어가 외부 API에 대한 접근에 의존하고 있는 경우, 자동화 테스트 중에 속도 제한이 발생할 수 있습니다. 이를 줄이기 위해, 환경 변수를 통해 인증 토큰을 제공하는 코드 경로가 있는지 확인하십시오. 예를 들어: ```shell cmd="curl --silent" if [ -n "$GITHUB_API_TOKEN" ]; then cmd="$cmd -H 'Authorization: token $GITHUB_API_TOKEN'" fi cmd="$cmd $releases_path" ``` ### `GITHUB_API_TOKEN` `GITHUB_API_TOKEN`를 사용할 때는, 오직 `public_repo` 액세스 권환으로 [새로운 개인 토큰](https://github.com/settings/tokens/new)을 생성합니다. 다음으로 이 토큰을 CI pipeline 환경 변수에 추가하십시오. ::: warning 경고 절대 인증 토큰을 코드 리포지토리에 공개해서는 안됩니다. ::: ## 플러그인 Shortname 인덱스 ::: tip 권장되는 플러그인 설치 방법은 URL을 바탕으로 직접 설치입니다: ```shell # asdf plugin add <name> <git_url> asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs ``` ::: `git_url`이 지정되지 않은 경우, asdf는 사용될 `git_url`을 정확히 결정하기 위해 [Shortname 인덱스 리포지토리](https://github.com/asdf-vm/asdf-plugins)를 사용합니다. [Shortname 인덱스](https://github.com/asdf-vm/asdf-plugins)에 설명서에 따라 플러그인을 해당 리포지토리에 추가할 수 있습니다. ================================================ FILE: docs/manage/commands.md ================================================ # All Commands The list of all commands available in `asdf`. This list is the `asdf help` command text. <<< @../../internal/help/help.txt ================================================ FILE: docs/manage/configuration.md ================================================ # Configuration Configuration of `asdf` encompasses both the sharable `.tool-versions` files as well as user specific customisations with `.asdfrc` and Environment Variables. ## `.tool-versions` Whenever `.tool-versions` file is present in a directory, the tool versions it declares will be used in that directory and any subdirectories. This is what a `.tool-versions` file looks like: ``` ruby 2.5.3 nodejs 10.15.0 ``` You can also include comments: ``` ruby 2.5.3 # This is a comment # This is another comment nodejs 10.15.0 ``` The versions can be in the following format: - `10.15.0` - an actual version. Plugins that support downloading binaries, will download binaries. - `ref:v1.0.2-a` or `ref:39cb398vb39` - tag/commit/branch to download from github and compile - `path:~/src/elixir` - a path to custom compiled version of a tool to use. For use by language developers and such. - `system` - this keyword causes asdf to passthrough to the version of the tool on the system that is not managed by asdf. ::: tip Multiple versions can be set by separating them with a space. For example, to use Python `3.7.2`, fallback to Python `2.7.15` and finally to the `system` Python, the following line can be added to `.tool-versions`. ``` python 3.7.2 2.7.15 system ``` ::: To install all the tools defined in a `.tool-versions` file run `asdf install` with no other arguments in the directory containing the `.tool-versions` file. To install a single tool defined in a `.tool-versions` file run `asdf install <name>` in the directory containing the `.tool-versions` file. The tool will be installed at the version specified in the `.tool-versions` file. Edit the file directly or use `asdf set` which updates it. ## `.asdfrc` The `.asdfrc` file defines the user's machine specific configuration. `${HOME}/.asdfrc` is the default location used by asdf. This can be set with the [Environment Variable `ASDF_CONFIG_FILE`](#asdf-config-file). The below file shows the required format with the default values: ```txt legacy_version_file = no use_release_candidates = no always_keep_download = no plugin_repository_last_check_duration = 60 disable_plugin_short_name_repository = no concurrency = auto ``` ### `legacy_version_file` Plugins **with support** can read the versions files used by other version managers, for example, `.ruby-version` in the case of Ruby's `rbenv`. | Options | Description | | :--------------------------------------------------------- | :------------------------------------------------------------------------- | | `no` <Badge type="tip" text="default" vertical="middle" /> | Use `.tool-versions` to read versions | | `yes` | Use plugin fallback to legacy version files (`.ruby-version`) if available | ### `always_keep_download` Configure the `asdf install` command to keep or delete the source code or binary it downloads. | Options | Description | | :--------------------------------------------------------- | :---------------------------------------------------- | | `no` <Badge type="tip" text="default" vertical="middle" /> | Delete source code or binary after successful install | | `yes` | Keep source code or binary after install | ### `plugin_repository_last_check_duration` Configure the duration (in minutes) between asdf plugin repository syncs. Trigger events result in a check of the duration. If more time has elapsed since the last sync than specified in the duration, a new sync occurs. | Options | Description | | :------------------------------------------------------------------------------------------------------ | :----------------------------------------------------------- | | integer in range `1` to `999999999` <br/> `60` is <Badge type="tip" text="default" vertical="middle" /> | Sync on trigger event if duration (in minutes) since last sync has been exceeded | | `0` | Sync on each trigger event | | `never` | Never sync | Sync events occur when the following commands are executed: - `asdf plugin add <name>` - `asdf plugin list all` `asdf plugin add <name> <git-url>` does NOT trigger a plugin sync. ::: warning Note Setting the value to `never` does not stop the plugin repository from being initially synced, for that behaviour see `disable_plugin_short_name_repository`. ::: ### `disable_plugin_short_name_repository` Disable synchronization of the asdf plugin short-name repository. Sync events will exit early if the short-name repository is disabled. | Options | Description | | :--------------------------------------------------------- | :-------------------------------------------------------- | | `no` <Badge type="tip" text="default" vertical="middle" /> | Clone or update the asdf plugin repository on sync events | | `yes` | Disable the plugin short-name repository | Sync events occur when the following commands are executed: - `asdf plugin add <name>` - `asdf plugin list all` `asdf plugin add <name> <git-url>` does NOT trigger a plugin sync. ::: warning Note Disabling the plugin short-name repository does not remove the repository if it has already synced. Remove the plugin repo with `rm --recursive --trash $ASDF_DATA_DIR/repository`. Disabling the plugin short-name repository does not remove plugins previously installed from this source. Plugins can be removed with `asdf plugin remove <name>`. Removing a plugin will remove all installed versions of the managed tool. ::: ### `concurrency` The default number of cores to use during compilation. | Options | Description | | :------ | :--------------------------------------------------------------------------------------------------- | | integer | Number of cores to use when compiling the source code | | `auto` | Calculate the number of cores using `nproc`, then `sysctl hw.ncpu`, then `/proc/cpuinfo` or else `1` | Note: the environment variable `ASDF_CONCURRENCY` take precedence if set. ### Plugin Hooks It is possible to execute custom code: - Before or after a plugin is installed, reshimed, updated, or uninstalled - Before or after a plugin command is executed For example, if a plugin called `foo` is installed and provides a `bar` executable, then the following hooks can be used to execute custom code first: ```text pre_foo_bar = echo Executing with args: $@ ``` The following patterns are supported: - `pre_<plugin_name>_<command>` - `pre_asdf_download_<plugin_name>` - `{pre,post}_asdf_{install,reshim,uninstall}_<plugin_name>` - `$1`: full version - `{pre,post}_asdf_plugin_{add,update,remove,reshim}` - `$1`: plugin name - `{pre,post}_asdf_plugin_{add,update,remove}_<plugin_name>` See [Create a Plugin](../plugins/create.md) for specifics on what command hooks are ran before or after what commands. ## Environment Variables Setting environment variables varies depending on your system and Shell. Default locations depend upon your installation location and method (Git clone, Homebrew, AUR). Environment variables should generally be set before sourcing `asdf.sh`/`asdf.fish` etc. For Elvish set above `use asdf`. The following describe usage with a Bash Shell. ### `ASDF_CONFIG_FILE` Path to the `.asdfrc` configuration file. Can be set to any location. Must be an absolute path. - If Unset: `$HOME/.asdfrc` will be used. - Usage: `export ASDF_CONFIG_FILE=/home/john_doe/.config/asdf/.asdfrc` ### `ASDF_TOOL_VERSIONS_FILENAME` The filename of the file storing the tool names and versions. Can be any valid filename. Typically, you should not set this value unless you want to ignore `.tool-versions` files. - If Unset: `.tool-versions` will be used. - Usage: `export ASDF_TOOL_VERSIONS_FILENAME=tool_versions` ### `ASDF_DIR` The location of `asdf` core scripts. Can be set to any location. Must be an absolute path. - If Unset: the parent directory of the `bin/asdf` executable is used. - Usage: `export ASDF_DIR=/home/john_doe/.config/asdf` ### `ASDF_DATA_DIR` The location where `asdf` will install plugins, shims and tool versions. Can be set to any location. Must be an absolute path. - If Unset: `$HOME/.asdf` if it exists, or else the value of `ASDF_DIR` - Usage: `export ASDF_DATA_DIR=/home/john_doe/.asdf` ### `ASDF_CONCURRENCY` Number of cores to use when compiling the source code. If set, this value takes precedence over the asdf config `concurrency` value. - If Unset: the asdf config `concurrency` value is used. - Usage: `export ASDF_CONCURRENCY=32` ## 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_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) | ================================================ FILE: docs/manage/core.md ================================================ # Core The core `asdf` command list is rather small, but can facilitate many workflows. ## Installation & Setup Covered in the [Getting Started](/guide/getting-started.md) guide. ## Exec ```shell asdf exec <command> [args...] ``` Executes the command shim for the current version. <!-- TODO: expand on this with example --> ## Env ```shell asdf env <command> [util] ``` <!-- TODO: expand on this with example --> ## Info ```shell asdf info ``` A helper command to print the OS, Shell and `asdf` debug information. Share this when making a bug report. ## Reshim ```shell asdf reshim <name> <version> ``` This recreates the shims for the current version of a package. By default, shims are created by plugins during installation of a tool. Some tools like the [npm CLI](https://docs.npmjs.com/cli/) allow global installation of executables, for example, installing [Yarn](https://yarnpkg.com/) via `npm install -g yarn`. Since this executable was not installed via the plugin lifecycle, no shim exists for it yet. `asdf reshim nodejs <version>` will force recalculation of shims for any new executables, like `yarn`, for `<version>` of `nodejs` . ## Shim-versions ```shell asdf shimversions <command> ``` Lists the plugins and versions that provide shims for a command. As an example, [Node.js](https://nodejs.org/) ships with two executables, `node` and `npm`. When many versions of the tools are installed with [`asdf-nodejs`](https://github.com/asdf-vm/asdf-nodejs/) `shimversions` can return: ```shell ➜ asdf shimversions node nodejs 14.8.0 nodejs 14.17.3 nodejs 16.5.0 ``` ```shell ➜ asdf shimversions npm nodejs 14.8.0 nodejs 14.17.3 nodejs 16.5.0 ``` ## Update Please use the same method you used to install asdf to update it. The latest version of asdf is shown in the top right corner of this page. ## Uninstall To uninstall `asdf` follow these steps: ::: details Bash & Git 1. In your `~/.bashrc` remove the lines that source `asdf.sh` and the completions: ```shell . "$HOME/.asdf/asdf.sh" . "$HOME/.asdf/completions/asdf.bash" ``` 2. Remove the `$HOME/.asdf` dir: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 3. Run this command to remove all `asdf` config files: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Bash & Git (macOS) 1. In your `~/.bash_profile` remove the lines that source `asdf.sh` and the completions: ```shell . "$HOME/.asdf/asdf.sh" . "$HOME/.asdf/completions/asdf.bash" ``` 2. Remove the `$HOME/.asdf` dir: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 3. Run this command to remove all `asdf` config files: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Bash & Homebrew 1. In your `~/.bashrc` remove the lines that source `asdf.sh` and the completions: ```shell . $(brew --prefix asdf)/libexec/asdf.sh . $(brew --prefix asdf)/etc/bash_completion.d/asdf.bash ``` Completions may have been [configured as per Homebrew's instructions](https://docs.brew.sh/Shell-Completion#configuring-completions-in-bash) so follow their guide to find out what to remove. 2. Uninstall with your package manager: ```shell brew uninstall asdf --force ``` 3. Run this command to remove all `asdf` config files: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Bash & Homebrew (macOS) If using **macOS Catalina or newer**, the default shell has changed to **ZSH**. If you can't find any config in your `~/.bash_profile` it may be in a `~/.zshrc` in which case please follow the ZSH instructions. 1. In your `~/.bash_profile` remove the lines that source `asdf.sh` and the completions: ```shell . $(brew --prefix asdf)/libexec/asdf.sh . $(brew --prefix asdf)/etc/bash_completion.d/asdf.bash ``` Completions may have been [configured as per Homebrew's instructions](https://docs.brew.sh/Shell-Completion#configuring-completions-in-bash) so follow their guide to find out what to remove. 2. Uninstall with your package manager: ```shell brew uninstall asdf --force ``` 3. Run this command to remove all `asdf` config files: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Bash & Pacman 1. In your `~/.bashrc` remove the lines that source `asdf.sh` and the completions: ```shell . /opt/asdf-vm/asdf.sh ``` 2. Uninstall with your package manager: ```shell pacman -Rs asdf-vm ``` 3. Remove the `$HOME/.asdf` dir: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 4. Run this command to remove all `asdf` config files: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Fish & Git 1. In your `~/.config/fish/config.fish` remove the lines that source `asdf.fish`: ```shell source ~/.asdf/asdf.fish ``` and remove completions with this command: ```shell rm -rf ~/.config/fish/completions/asdf.fish ``` 2. Remove the `$HOME/.asdf` dir: ```shell rm -rf (string join : -- $ASDF_DATA_DIR $HOME/.asdf) ``` 3. Run this command to remove all `asdf` config files: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Fish & Homebrew 1. In your `~/.config/fish/config.fish` remove the lines that source `asdf.fish`: ```shell source "(brew --prefix asdf)"/libexec/asdf.fish ``` 2. Uninstall with your package manager: ```shell brew uninstall asdf --force ``` 3. Run this command to remove all `asdf` config files: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Fish & Pacman 1. In your `~/.config/fish/config.fish` remove the lines that source `asdf.fish`: ```shell source /opt/asdf-vm/asdf.fish ``` 2. Uninstall with your package manager: ```shell pacman -Rs asdf-vm ``` 3. Remove the `$HOME/.asdf` dir: ```shell rm -rf (string join : -- $ASDF_DATA_DIR $HOME/.asdf) ``` 4. Run this command to remove all `asdf` config files: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Elvish & Git 1. In your `~/.config/elvish/rc.elv` remove the lines that use the `asdf` module: ```shell use asdf _asdf; var asdf~ = $_asdf:asdf~ set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~ ``` and uninstall the `asdf` module with this command: ```shell rm -f ~/.config/elvish/lib/asdf.elv ``` 2. Remove the `$HOME/.asdf` dir: ```shell if (!=s $E:ASDF_DATA_DIR "") { rm -rf $E:ASDF_DATA_DIR } else { rm -rf ~/.asdf } ``` 3. Run this command to remove all `asdf` config files: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Elvish & Homebrew 1. In your `~/.config/elvish/rc.elv` remove the lines that use the `asdf` module: ```shell use asdf _asdf; var asdf~ = $_asdf:asdf~ set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~ ``` and uninstall the `asdf` module with this command: ```shell rm -f ~/.config/elvish/lib/asdf.elv ``` 2. Uninstall with your package manager: ```shell brew uninstall asdf --force ``` 3. Run this command to remove all `asdf` config files: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Elvish & Pacman 1. In your `~/.config/elvish/rc.elv` remove the lines that use the `asdf` module: ```shell use asdf _asdf; var asdf~ = $_asdf:asdf~ set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~ ``` and uninstall the `asdf` module with this command: ```shell rm -f ~/.config/elvish/lib/asdf.elv ``` 2. Uninstall with your package manager: ```shell pacman -Rs asdf-vm ``` 3. Remove the `$HOME/.asdf` dir: ```shell if (!=s $E:ASDF_DATA_DIR "") { rm -rf $E:ASDF_DATA_DIR } else { rm -rf ~/.asdf } ``` 4. Run this command to remove all `asdf` config files: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details ZSH & Git 1. In your `~/.zshrc` remove the lines that source `asdf.sh` and completions: ```shell . "$HOME/.asdf/asdf.sh" # ... fpath=(${ASDF_DIR}/completions $fpath) autoload -Uz compinit compinit ``` **OR** the ZSH Framework plugin if used. 2. Remove the `$HOME/.asdf` dir: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 3. Run this command to remove all `asdf` config files: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details ZSH & Homebrew 1. In your `~/.zshrc` remove the lines that source `asdf.sh`: ```shell . $(brew --prefix asdf)/libexec/asdf.sh ``` 2. Uninstall with your package manager: ```shell brew uninstall asdf --force && brew autoremove ``` 3. Run this command to remove all `asdf` config files: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details ZSH & Pacman 1. In your `~/.zshrc` remove the lines that source `asdf.sh`: ```shell . /opt/asdf-vm/asdf.sh ``` 2. Uninstall with your package manager: ```shell pacman -Rs asdf-vm ``` 3. Remove the `$HOME/.asdf` dir: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 4. Run this command to remove all `asdf` config files: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: That's it! 🎉 ================================================ FILE: docs/manage/dependencies.md ================================================ # Dependencies This list is for asdf version 0.16.0 and greater. Older versions of asdf have additional dependencies. asdf itself requires the following to be installed: * Bash version `3.2.48` * Git version `1.7.7.2` ::: tip Note Note that asdf plugins may require additional programs to be installed before they can be used. Read the plugin's documentation and verify you have all of the plugin's dependencies installed before installing it. ::: ## Install If you need to manually install dependencies for asdf run the command for your OS below. <!--@include: @/parts/install-dependencies-cmds.md--> ::: tip Note `sudo` may be required depending on your system configuration. ::: ================================================ FILE: docs/manage/plugins.md ================================================ # Plugins Plugins are how `asdf` knows to handle different tools like Node.js, Ruby, Elixir etc. See [Creating Plugins](/plugins/create.md) for the plugin API used to support more tools. ## Add Add plugins via their Git URL: ```shell asdf plugin add <name> <git-url> # asdf plugin add elm https://github.com/vic/asdf-elm ``` or via the short-name association in the plugins repository: ```shell asdf plugin add <name> # asdf plugin add erlang ``` ::: tip Recommendation Prefer the longer `git-url` method as it is independent of the short-name repo. ::: ## List Installed ```shell asdf plugin list # asdf plugin list # java # nodejs ``` ```shell asdf plugin list --urls # asdf plugin list # java https://github.com/halcyon/asdf-java.git # nodejs https://github.com/asdf-vm/asdf-nodejs.git ``` ## List All in Short-name Repository ```shell asdf plugin list all ``` See [Plugins Shortname Index](https://github.com/asdf-vm/asdf-plugins) for the entire short-name list of plugins. ## Update ```shell asdf plugin update --all ``` If you want to update a specific package, just say so. ```shell asdf plugin update <name> # asdf plugin update erlang ``` This update will fetch the _latest commit_ on the _default branch_ of the _origin_ of the plugin repository. Versioned plugins and updates are currently being developed ([#916](https://github.com/asdf-vm/asdf/pull/916)) ## Remove ```bash asdf plugin remove <name> # asdf plugin remove erlang ``` Removing a plugin will remove all installations of the tool made with the plugin. This can be used as a shorthand for cleaning/pruning many unused versions of a tool. ## Syncing the asdf Short-name Repository The short-name repo is synced to your local machine and periodically refreshed. This method to determine a sync is as follows: - sync events are triggered by commands: - `asdf plugin add <name>` - `asdf plugin list all` - if configuration option `disable_plugin_short_name_repository` is set to `yes`, then sync is aborted early. See the [asdf config docs](/manage/configuration.md) for more. - if there has not been a synchronization in the last `X` minutes then the sync will occur. - `X` defaults to `60`, but can be configured in your `.asdfrc` via the `plugin_repository_last_check_duration` option. See the [asdf config docs](/manage/configuration.md) for more. ================================================ FILE: docs/manage/versions.md ================================================ # Versions ## Install Version ```shell asdf install <name> <version> # asdf install erlang 17.3 ``` If a plugin supports downloading & compiling from source, you can specify `ref:foo` where `foo` is a specific branch, tag, or commit. You'll need to use the same name and reference when uninstalling too. ## Install Latest Stable Version ```shell asdf install <name> latest # asdf install erlang latest ``` Install latest stable version that begins with a given string. ```shell asdf install <name> latest:<version> # asdf install erlang latest:17 ``` ## List Installed Versions ```shell asdf list <name> # asdf list erlang ``` Filter versions to those that begin with a given string. ```shell asdf list <name> <version> # asdf list erlang 17 ``` ## List All Available Versions ```shell asdf list all <name> # asdf list all erlang ``` Filter versions to those that begin with a given string. ```shell asdf list all <name> <version> # asdf list all erlang 17 ``` ## Show Latest Stable Version ```shell asdf latest <name> # asdf latest erlang ``` Show latest stable version that begins with a given string. ```shell asdf latest <name> <version> # asdf latest erlang 17 ``` ## Set Version #### Via `.tool-versions` file ```shell asdf set [flags] <name> <version> [<version>...] # asdf set elixir 1.2.4 # set in current dir # asdf set -u elixir 1.2.4 # set in .tool-versions file in home directory # asdf set -p elixir 1.2.4 # set in existing .tool-versions file in a parent dir asdf set <name> latest[:<version>] # asdf set elixir latest ``` `asdf set` writes the version to a `.tool-versions` file in the current directory, creating it if needed. It exists purely for convenience. You can think of it as just doing `echo "<tool> <version>" > .tool-versions`. With the `-u`/`--home` flag `asdf set` writes to the `.tool-versions` file in your `$HOME` directory, creating the file if it does not exist. With the `-p`/`--parent` flag `asdf set` finds a `.tool-versions` file in the closest parent directory of the current directory. #### Via Environment Variable When determining the version looks for an environment variable with the pattern `ASDF_${TOOL}_VERSION`. The version format is the same supported by the `.tool-versions` file. If set, the value of this environment variable overrides any versions set in for the tool in any `.tool-versions` file. For example: ```shell export ASDF_ELIXIR_VERSION=1.18.1 ``` Will tell asdf to use Elixir `1.18.1` in the current shell session. :::warning Because this is an environment variable, it only takes effect where it is set. Any other shell sessions that are running will still use to whatever version is set in a `.tool-versions` file. See the `.tool-versions` [file in the Configuration section](/manage/configuration.md) for details. ::: The following example runs tests on an Elixir project with version `1.4.0`. ```shell ASDF_ELIXIR_VERSION=1.4.0 mix test ``` ## Fallback to System Version To use the system version of tool `<name>` instead of an asdf managed version you can set the version for the tool to `system`. Set system with either `asdf set` or via environment variable as outlined in [Set Version](#set-version) section above. ```shell asdf set <name> system # asdf set python system ``` ## View Current Version ```shell asdf current # asdf current # erlang 17.3 /Users/kim/.tool-versions # nodejs 6.11.5 /Users/kim/cool-node-project/.tool-versions asdf current <name> # asdf current erlang # erlang 17.3 /Users/kim/.tool-versions ``` ## Uninstall Version ```shell asdf uninstall <name> <version> # asdf uninstall erlang 17.3 ``` ## Shims When asdf installs a package it creates shims for every executable program in that package in a `$ASDF_DATA_DIR/shims` directory (default `~/.asdf/shims`). This directory being on the `$PATH` (by means of `asdf.sh`, `asdf.fish`, etc) is how the installed programs are made available in the environment. The shims themselves are really simple wrappers that `exec` a helper program `asdf exec` passing it the name of the plugin and path to the executable in the installed package that the shim is wrapping. The `asdf exec` helper determines the version of the package to use (as specified in `.tool-versions` file or environment variable), the final path to the executable in the package installation directory (this can be manipulated by the `exec-path` callback in the plugin) and the environment to execute in (also provided by the plugin - `exec-env` script), and finally it executes it. ::: warning Note Because this system uses `exec` calls, any scripts in the package that are meant to be sourced by the shell instead of executed need to be accessed directly instead of via the shim wrapper. The two `asdf` commands: `which` and `where` can help with this by returning the path to the installed package: ::: ```shell # returns path to main executable in current version source $(asdf which ${PLUGIN})/../script.sh # returns path to the package installation directory source $(asdf where ${PLUGIN})/bin/script.sh ``` ### By-passing asdf shims If for some reason you want to by-pass asdf shims or want your environment variables automatically set upon entering your project's directory, the [asdf-direnv](https://github.com/asdf-community/asdf-direnv) plugin can be helpful. Be sure to check its README for more details. ================================================ FILE: docs/more/community-projects.md ================================================ # Community Projects Here are some community projects related to `asdf`: - [asdf-community](https://github.com/asdf-community): A collaborative, community-driven project for long-term maintenance of asdf plugins. - [asdf dev container](https://github.com/iloveitaly/asdf-devcontainer): A [GitHub Dev Container](https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/introduction-to-dev-containers) supporting asdf managed tools in GitHub Codespaces. ::: warning Note asdf core team do not own these projects or their code. asdf core are not responsible for the quality or security as they relate to those listed here. ::: ================================================ FILE: docs/more/faq.md ================================================ # FAQ Here are some common questions regarding `asdf`. ## WSL1 support? WSL1 ([Windows Subsystem for Linux](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux) 1) is not officially supported. Some aspects of `asdf` may not work properly. We do not intend to add official support for WSL1. ## WSL2 support? WSL2 ([Windows Subsystem for Linux](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux#WSL_2) 2) should work using the setup & dependency instructions for you chosen WSL distro. Importantly, WSL2 is _only_ expected to work properly when the current working directory is a Unix drive and not a bound Windows drive. We intend to run out test suite on WSL2 when host runner support is available on GitHub Actions, currently this does not appear to be the case. ## Newly installed executable not running? > I just `npm install -g yarn`, but cannot execute `yarn`. What gives? `asdf` uses [shims](<https://en.wikipedia.org/wiki/Shim_(computing)>) to manage executables. Those installed by plugins have shims automatically created, whereas installing executables via an `asdf` managed tool will require you to notify `asdf` of the need to create shims. In this instance, to create a shim for [Yarn](https://yarnpkg.com/). See the [`asdf reshim` command docs](/manage/core.md#reshim). ## Shell not detecting newly installed shims? If `asdf reshim` is not resolving your issue, then it is most-likely due to the sourcing of `asdf.sh` or `asdf.fish` _not_ being at the **BOTTOM** of your Shell config file (`.bash_profile`, `.zshrc`, `config.fish` etc). It needs to be sourced **AFTER** you have set your `$PATH` and **AFTER** you have sourced your framework (oh-my-zsh etc) if any. ## Why can't I use a version of `latest` in the `.tool-versions` file? asdf must always have an exact version of every tool in the current directory, not version ranges or special values like `latest` are not permitted. This ensure that asdf behaves in a deterministic and consistent way across time and across different machines. A special version like `latest` would change over time, and could vary between machines if `asdf install` was run at different times. As such it's allowed in asdf commands like `asdf set <tool> latest`, but forbidden in the `.tool-versions` file. Think of `.tool-versions` file as `Gemfile.lock` or `package-lock.json`. It is a file that contains the exact version of every tool your project depends on. Note that the `system` version is allowed in `.tool-versions` files, and it could resolve to different versions when used. It is a special value that effectively disables asdf for a particular tool in the given directory. See issue https://github.com/asdf-vm/asdf/issues/1012 ## Why can't version ranges be used in the `.tool-versions` files? Similar to the question above on the use of `latest`. With a version range specified, asdf would be free to choose any installed version in the specified range. This could result in different behavior across machines if they have different versions installed. The intent is for asdf to be fully deterministic so the same `.tool-versions` file produces the exact same environment across time and across different computers. See issue https://github.com/asdf-vm/asdf-nodejs/issues/235#issuecomment-885809776 ## Why is a command completely unrelated to the plugins I'm using is getting shimmed by asdf? **asdf is only going to generate shims for executables it manages**. If for example you use the Ruby plugin then you'll expect to see commands like `ruby` and `irb` replaced with shims, along with other executables present in Ruby packages you installed. If you see a shim that you don't expect, it's likely because you installed a package under a tool that was managed by asdf, and the package provided the executable. This is surprising when the executable has the same name as an executable already on your system. [Some users reported](https://github.com/asdf-vm/asdf/issues/584) a Node.JS package that provided it's own version of the `which` command. This resulted in asdf creating a shim for it, and it replacing the version of the `which` command present on the operating system. In such instances it's best to locate the package introducing the executable and remove it. `asdf which <command>` is helping in showing you where the offending executable is located, making it possible to determine what package added it. See issues https://github.com/asdf-vm/asdf/issues/584 https://github.com/asdf-vm/asdf/issues/1653 ================================================ FILE: docs/more/thanks.md ================================================ # Thanks Thank you page to the asdf authors & contributors! ## Credits Me ([@HashNuke](https://github.com/HashNuke)), High-fever, cold, cough. Copyright 2014 to the end of time ([MIT License](https://github.com/asdf-vm/asdf/blob/master/LICENSE)) ## Maintainers - [@HashNuke](https://github.com/HashNuke) - [@danhper](https://github.com/danhper) - [@Stratus3D](https://github.com/Stratus3D) - [@vic](https://github.com/vic) - [@jthegedus](https://github.com/jthegedus) ## Contributors See the [list of contributors](https://github.com/asdf-vm/asdf/graphs/contributors) :pray: on GitHub ================================================ FILE: docs/package.json ================================================ { "type": "module", "scripts": { "fmt": "prettier --write '.vitepress/{config,navbars,sidebars}.ts' '.vitepress/theme/**/*'", "dev": "vitepress dev", "build": "vitepress build", "preview": "vitepress preview" }, "devDependencies": { "@types/node": "^25.3.3", "prettier": "^3.8.1", "vitepress": "^1.6.4" } } ================================================ FILE: docs/parts/install-dependencies-cmds.md ================================================ | OS | Package Manager | Command | | ----- | --------------- | ---------------------------------- | | linux | Aptitude | `apt install git bash` | | linux | DNF | `dnf install git bash` | | linux | Pacman | `pacman -S git bash` | | linux | Zypper | `zypper install git bash` | | macOS | Homebrew | `brew install coreutils git bash` | | macOS | Spack | `spack install coreutils git bash` | ================================================ FILE: docs/parts/install-dependencies.md ================================================ ##### Install Dependencies asdf primarily requires `git`. Here is a _non-exhaustive_ list of commands to run for _your_ package manager (some might automatically install these tools in later steps). <!--@include: @/parts/install-dependencies-cmds.md--> ::: tip Note `sudo` may be required depending on your system configuration. ::: Please see the [Dependencies page](/manage/dependencies) for more information. ================================================ FILE: docs/plugins/create.md ================================================ # Create a Plugin 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 <name>`, `asdf install <name> <version>` 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-<tool_name>`) 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-<tool_name>` 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) <Badge type="tip" text="required" vertical="middle" /> | List all installable versions | | [bin/download](#bin-download) <Badge type="warning" text="recommended" vertical="middle" /> | Download source code or binary for the specified version | | [bin/install](#bin-install) <Badge type="tip" text="required" vertical="middle" /> | Installs the specified version | | [bin/latest-stable](#bin-latest-stable) <Badge type="warning" text="recommended" vertical="middle" /> | 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` | previous `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` <Badge type="tip" text="required" vertical="middle" /> **Description** List all installable versions. **Output Format** Must print a string with a **space-separated** list of versions. For example: ```txt 1.0.1 1.0.2 1.3.0 1.4 ``` Newest version should be last. asdf core will print each version on its own line, potentially pushing some versions offscreen. **Sorting** 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. 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 <name> [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 "${plugin_path}/bin/list-all" ``` --- ### `bin/download` <Badge type="tip" text="required" vertical="middle" /> **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 <tool> [version]` - `asdf install <tool> 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 "${plugin_path}"/bin/download ``` --- ### `bin/install` <Badge type="tip" text="required" vertical="middle" /> **Description** Install a specific version of a tool to a specified location. **Implementation Details** - 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](#bin-list-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. **Legacy Plugins** If the `bin/download` script is absent, this script should download **and** install the specified version. 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. **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_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. **Commands that invoke this script** - `asdf install` - `asdf install <tool>` - `asdf install <tool> [version]` - `asdf install <tool> latest[:version]` - `asdf install nodejs 18.0.0`: installs Node.js version `18.0.0` in the `ASDF_INSTALL_PATH` directory. **Call signature from asdf core** No parameters provided. ```bash "${plugin_path}"/bin/install ``` ## Optional Scripts ### `bin/latest-stable` <Badge type="warning" text="recommended" vertical="middle" /> **Description** Determine the latest stable version of a tool. If absent, the asdf core will `tail` the `bin/list-all` output which may be undesirable. **Implementation Details** - 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. **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 set <tool> latest`: set the 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. **Call signature from asdf core** The script should accept a single argument, the filter query. ```bash "${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 "${plugin_path}"/bin/help.overview ``` --- ### `bin/help.deps` **Description** Output the list of dependencies tailored to the operating system. One dependency per line. ```bash 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 "${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 "${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 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 "${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 bin tools 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 "${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 "${plugin_path}/bin/exec-env" ``` --- ### `bin/exec-path` 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. **Description** Get the executable path for the specified version of the tool. **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: plugin/bin/exec-path <install-path> <command> <executable-path> Example Call: ~/.asdf/plugins/foo/bin/exec-path "~/.asdf/installs/foo/1.0" "foo" "bin/foo" Output: bin/foox ``` **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 "${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 "${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 .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 "${plugin_path}/bin/list-legacy-filenames" ``` --- ### `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** - 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 1.2.3 ``` **Environment Variables available to script** No environment variables specifically set before this script is called. **Commands that invoke this script** Any command which reads a tool version. **Call signature from asdf core** The script should accept a single argument, the path to the legacy file for reading its contents. ```bash "${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}` **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 "${plugin_path}/bin/post-plugin-add" ``` --- ### `bin/post-plugin-update` **Description** Execute this callback script **after** asdf has downloaded the _update_ plugin with `asdf plugin update <tool> [<git-ref>]`. See also the related command hooks: - `pre_asdf_plugin_update` - `pre_asdf_plugin_update_${plugin_name}` - `post_asdf_plugin_update` - `post_asdf_plugin_update_${plugin_name}` **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 "${plugin_path}/bin/post-plugin-update" ``` --- ### `bin/pre-plugin-remove` **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}` **Environment Variables available to script** - `ASDF_PLUGIN_PATH`: path where the plugin was installed. **Call signature from asdf core** No parameters provided. ```bash "${plugin_path}/bin/pre-plugin-remove" ``` <!-- TODO: document command hooks --> <!-- ## Command Hooks --> ## 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 foo/ lib/commands/ command.bash command-bat.bash command-bat-man.bash command-help.bash ``` Users can now execute: ```shell $ 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` $ asdf foo bat man # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-bat-man.bash` $ asdf foo bat baz # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-bat.bash baz` ``` Plugin authors can use this feature to provide utilities related to their tools, or even create plugins that are just new command extensions of asdf itself. If the executable bit is set, the script is executed, replacing the asdf execution. If the executable bit is not set, asdf will source the scripts as Bash scripts. `$ASDF_CMD_FILE` resolves to the full path of the file being sourced. [`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. Be sure to list your asdf Extension Commands in your plugins README. ## Custom Shim Templates <Badge type="danger" text="advanced" vertical="middle" /> ::: warning Please only use if **absolutely** required ::: 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. **This must be used wisely.** 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. ## Testing `asdf` contains the `plugin-test` command to test your plugin: ```shell asdf plugin test <plugin_name> <plugin_url> [--asdf-tool-version <version>] [--asdf-plugin-gitref <git_ref>] [test_command...] ``` - `<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. Defaults to the default branch of the plugin's repository. - 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 # 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 ::: ### GitHub Action 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 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" ``` ### TravisCI Config A sample `.travis.yml` file, customize it to your needs ```yaml language: c 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 os: - linux - osx ``` ::: tip NOTE When using another CI you may need to pass a relative path to the plugin location: ```shell asdf plugin test <tool_name> <path> '<tool_command> --version' ``` ::: ## API Rate Limiting 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 --silent" if [ -n "$GITHUB_API_TOKEN" ]; then cmd="$cmd -H 'Authorization: token $GITHUB_API_TOKEN'" fi cmd="$cmd $releases_path" ``` ### `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 # asdf plugin add <name> <git_url> asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs ``` ::: 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. You can add your plugin to the [Shortname Index](https://github.com/asdf-vm/asdf-plugins) by following the instructions in that repo. ================================================ FILE: docs/pt-br/contribute/core.md ================================================ # asdf > Hi, we've recently migrated our docs and added some new pages. If you would like to help translate this page, see the "Edit this page" link at the bottom of the page. guia de contribuição principal `asdf`. ## Configuração inicial Fork `asdf` no GitHub e/ou Git clone o branch padrão: ```shell # clone your fork git clone https://github.com/<GITHUB_USER>/asdf.git # or clone asdf git clone https://github.com/asdf-vm/asdf.git ``` As ferramentas para o desenvolvimento do núcleo estão em `.tool-versions` deste repositório. Se você deseja gerenciar com o próprio `asdf`, adicione os plugins: ```shell asdf plugin add bats https://github.com/timgluz/asdf-bats.git asdf plugin add shellcheck https://github.com/luizm/asdf-shellcheck.git asdf plugin add shfmt https://github.com/luizm/asdf-shfmt.git ``` Instale as versões para desenvolver `asdf` com: ```shell asdf install ``` _pode_ ser útil não usar `asdf` para gerenciar as ferramentas durante o desenvolvimento em sua máquina local, pois você pode precisar quebrar funcionalidades que, então, quebrariam suas ferramentas de desenvolvimento. Aqui está a lista bruta de ferramentas: - [bats-core](https://github.com/bats-core/bats-core): Bash Automated Testing System, para testes unitários de scripts compatíveis com Bash ou POSIX. - [shellcheck](https://github.com/koalaman/shellcheck): Ferramenta de análise estática para scripts de shell. - [shfmt](https://github.com/mvdan/sh): Um analisador, formatador e interpretador de shell com suporte a bash; inclui shfmt ## Desenvolvimento Se você quiser testar suas alterações sem fazer alterações em seu `asdf` instalado, você pode definir a variável `$ASDF_DIR` para o caminho onde você clonou o repositório e anexar temporariamente o diretório `bin` e `shims` do diretório para o seu caminho. É melhor formatar, lint e testar seu código localmente antes de confirmar ou enviar para o controle remoto. Use os seguintes scripts/comandos: ```shell # Shellcheck ./scripts/shellcheck.bash # Format ./scripts/shfmt.bash # Test: all tests bats test/ # Test: for specific command bats test/list_commands.bash ``` ::: tip **Adicione testes!** - Os testes são **necessários** para novos recursos e aceleram a revisão de correções de bugs. Por favor, cubra novos caminhos de código antes de criar um Pull Request. Consulte [documentação do bats-core](https://bats-core.readthedocs.io/en/stable/index.html) ::: ## Teste de BATS É **fortemente recomendado** examinar o conjunto de testes existente e a [documentação do bats-core](https://bats-core.readthedocs.io/en/stable/index.html) antes de escrever os testes. A depuração de BATs pode ser difícil às vezes. Usar a saída TAP com o sinalizador `-t` permitirá que você imprima saídas com o descritor de arquivo especial `>&3` durante a execução do teste, simplificando a depuração. Como um exemplo: ```shell # test/some_tests.bats printf "%s\n" "Will not be printed during bats test/some_tests.bats" printf "%s\n" "Will be printed during bats -t test/some_tests.bats" >&3 ``` Isso está documentado em bats-core [Imprimindo no Terminal](https://bats-core.readthedocs.io/en/stable/writing-tests.html#printing-to-the-terminal). ## Pull Requests, Releases e Commits Convencionais O `asdf` está usando uma ferramenta de lançamento automatizada chamada [Release Please](https://github.com/googleapis/release-please) para aumentar automaticamente a versão [SemVer](https://semver.org/) e gerar a [Changelog](https://github.com/asdf-vm/asdf/blob/master/CHANGELOG.md). Essas informações são determinadas lendo o histórico de confirmação desde a última versão. [Mensagens de confirmação convencionais](https://www.conventionalcommits.org/) definem o formato do título da solicitação pull que se torna o formato da mensagem de confirmação na ramificação padrão. Isso é aplicado com GitHub Action [`amannn/action-semantic-pull-request`](https://github.com/amannn/action-semantic-pull-request). O Commit Convencional segue este formato: ``` <type>[optional scope][optional !]: <description> <!-- examples --> fix: some fix feat: a new feature docs: some documentation update docs(website): some change for the website feat!: feature with breaking change ``` A lista completa de `<types>` é: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `build`, `ci`, `chore`, `revert`. O `!` indica uma mudança de ruptura. `fix`: will create a new SemVer `patch` `feat`: will create a new SemVer `minor` `<type>!`: will create a new SemVer `major` O título da solicitação pull deve seguir este formato. ::: tip Use o formato de mensagem de confirmação convencional para seu título de solicitação de pull. ::: ## Imagens Docker Os projetos [asdf-alpine](https://github.com/vic/asdf-alpine) e [asdf-ubuntu](https://github.com/vic/asdf-ubuntu) são um esforço contínuo para fornecer imagens de algumas ferramentas asdf. Você pode usar essas imagens docker como base para seus servidores de desenvolvimento ou para executar seus aplicativos de produção. ================================================ FILE: docs/pt-br/contribute/documentation.md ================================================ # Docs & Site > Hi, we've recently migrated our docs and added some new pages. If you would like to help translate this page, see the "Edit this page" link at the bottom of the page. Documentação e guia de contribuição do site. ## Configuração inicial Fork `asdf` no GitHub e/ou Git clone o branch padrão: ```shell # clone your fork git clone https://github.com/<GITHUB_USER>/asdf.git # or clone asdf git clone https://github.com/asdf-vm/asdf.git ``` As ferramentas para desenvolvimento de sites Docs são gerenciadas com `asdf` em `docs/.tool-versions`. Adicione os plugins com: ```shell asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs ``` Instale a(s) versão(ões) da ferramenta com: ```shell asdf install ``` - [Node.js](https://nodejs.org): tempo de execução JavaScript criado no mecanismo JavaScript V8 do Chrome. - Instale as dependências do Node.js do `docs/package.json`: ```shell npm install ``` ## Desenvolvimento [Vuepress (v2)](https://v2.vuepress.vuejs.org/) é o Static Site Generator (SSG) que usamos para construir o site de documentação do asdf. Foi escolhido para substituir [Docsify.js](https://docsify.js.org/), pois gostaríamos de oferecer suporte a um substituto somente HTML quando os usuários não tiverem JavaScript disponível ou ativado. Isso não era possível com o Docsify. Fora isso, o conjunto de recursos é basicamente o mesmo, com foco em escrever arquivos Markdown com configuração mínima. `package.json` contém os scripts necessários para o desenvolvimento: @[code json{3-5}](../package.json) Para iniciar o servidor de desenvolvimento local: ```shell npm run dev ``` Formate o código antes de confirmar: ```shell npm run format ``` ## Pull Requests, Releases e Commits Convencionais `asdf` está usando um pipeline de lançamento automatizado que depende de Commits Convencionais em títulos de PR. Documentação detalhada encontrada no [guia de contribuição principal](./core.md). Ao criar um PR para alterações na documentação, por favor, faça o título do PR com o tipo de Commit Convencional `docs` no formato `docs: <description>`. ## Vuepress A configuração do site está contida em alguns arquivos JavaScript com objetos JS usados para representar a configuração. Eles estão: - `docs/.vuepress/config.js`: o arquivo de configuração raiz do site. Leia a [documentação do Vuepress](https://v2.vuepress.vuejs.org/guide/configuration.html#config-file) para obter as especificações. Para simplificar a configuração raiz, os objetos JS maiores que representam a configuração _navbar_ e _sidebar_ foram extraídos e separados por sua localidade. Veja os dois em: - `docs/.vuepress/navbar.js` - `docs/.vuepress/sidebar.js` Com a documentação oficial para essas configurações vivendo na [Referência de tema padrão](https://v2.vuepress.vuejs.org/reference/default-theme/config.html#locale-config). ## I18n Vuepress tem suporte de primeira classe para internacionalização. O root config `docs/.vuepress/config.js` define os locais suportados com sua URL, título no menu suspenso de seleção e referências de configurações navbar/sidebar. As configurações da barra de navegação/barra lateral são capturadas nos arquivos de configuração mencionados acima, separadas por localidade e exportadas individualmente. O conteúdo de markdown para cada localidade deve estar em uma pasta com o mesmo nome das chaves para `locales` na configuração raiz. Isso é: ```js { ... themeConfig: { locales: { "/": { selectLanguageName: "English (US)", sidebar: sidebar.en, navbar: navbar.en }, "/pt-BR/": { selectLanguageName: "Brazilian Portuguese", sidebar: sidebar.pt_br, navbar: navbar.pt_br } } } } ``` `/pt-BR/` exigirá o mesmo conjunto de arquivos markdown localizados em `docs/pt-BR/`, assim: ```shell docs ├─ README.md ├─ foo.md ├─ nested │ └─ README.md └─ pt-BR ├─ README.md ├─ foo.md └─ nested └─ README.md ``` A [documentação oficial do Vuepress i18n](https://v2.vuepress.vuejs.org/guide/i18n.html#site-i18n-config) entra em mais detalhes. ================================================ FILE: docs/pt-br/contribute/first-party-plugins.md ================================================ # First-Party Plugins > Hi, we've recently migrated our docs and added some new pages. If you would like to help translate this page, see the "Edit this page" link at the bottom of the page. A equipe principal do asdf criou alguns plugins relevantes para sua vida profissional diária. A ajuda é sempre bem-vinda na manutenção e melhoria desses plugins. Veja o repositório associado para cada link abaixo: - [Elixir](https://github.com/asdf-vm/asdf-elixir) - [Erlang](https://github.com/asdf-vm/asdf-erlang) - [Node.js](https://github.com/asdf-vm/asdf-nodejs) - [Ruby](https://github.com/asdf-vm/asdf-ruby) Para plugins da comunidade, consulte: - [`asdf-community` organisation](https://github.com/asdf-community): A collaborative, community-driven project for long-term maintenance of `asdf` plugins. - [`asdf-plugins` shortname repo](https://github.com/asdf-vm/asdf-plugins): Short-name list used by `asdf` core to lookup popular `asdf` plugins. - [GitHub `asdf-plugin` topic search](https://github.com/topics/asdf-plugin) ================================================ FILE: docs/pt-br/contribute/github-actions.md ================================================ # GitHub Actions > Hi, we've recently migrated our docs and added some new pages. If you would like to help translate this page, see the "Edit this page" link at the bottom of the page. Obrigado pelo seu interesse, consulte o [repositório de ações asdf](https://github.com/asdf-vm/actions) para ver os problemas, conversas e diretrizes de contribuição existentes. ================================================ FILE: docs/pt-br/guide/getting-started.md ================================================ # Começando > Hi, we've recently migrated our docs and added some new pages. If you would like to help translate this page, see the "Edit this page" link at the bottom of the page. A instalação do `asdf` envolve: 1. Instalar as dependências 2. Instalar o núcleo do `asdf` 3. Adicionar o `asdf` ao seu shell 4. Instalar um plugin para cada ferramenta que você gostaria de gerenciar 5. Instalar uma versão desta ferramenta 6. Definir uma versão global e uma versão local através do arquivo de configuração `.tool-versions` Você pode também acompanhar o passo a passo da instalação através [deste vídeo](https://youtu.be/8W3xaSPjeog). ## 1. Instalando as dependências asdf primarily requires `git` & `curl`. Here is a _non-exhaustive_ list of commands to run for _your_ package manager (some might automatically install these tools in later steps). | OS | Package Manager | Command | | ----- | --------------- | ---------------------------------- | | linux | Aptitude | `apt install curl git` | | linux | DNF | `dnf install curl git` | | linux | Pacman | `pacman -S curl git` | | linux | Zypper | `zypper install curl git` | | macOS | Homebrew | `brew install coreutils curl git` | | macOS | Spack | `spack install coreutils curl git` | ::: tip Note `sudo` may be required depending on your system configuration. ::: ## 2. Download asdf ### Official Download <!-- x-release-please-start-version --> ```shell git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.18.1 ``` <!-- x-release-please-end --> ### Community Supported Download Methods We highly recommend using the official `git` method. | Method | Command | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Homebrew | `brew install asdf` | | Pacman | `git clone https://aur.archlinux.org/asdf-vm.git && cd asdf-vm && makepkg -si` or use your preferred [AUR helper](https://wiki.archlinux.org/index.php/AUR_helpers) | ## 3. Adicionando ao seu shell Existem diversas combinações de shells, sistemas operacionais e métodos de instalação que podem impactar a configuração. Abaixo, expanda a seção que se adeque mais com o seu sistema: ::: details Bash & Git Adicione esta linha ao seu `~/.bashrc`: ```shell . "$HOME/.asdf/asdf.sh" ``` O auto completar deve ser configurado manualmente a partir da adição da seguinte linha ao `.bashrc`: ```shell . "$HOME/.asdf/completions/asdf.bash" ``` ::: ::: details Bash & Git (macOS) Se você estiver usando o **macOS Catalina ou mais recente**, o shell padrão mudou para o **ZSH**. A não ser que você tenha voltado para o bash, siga as instruções de instalação para o ZSH. Adicione esta linha ao seu `~/.bash_profile`: ```shell . "$HOME/.asdf/asdf.sh" ``` O auto completar deve ser configurado manualmente a partir da adição da seguinte linha ao `.bash_profile`: ```shell . "$HOME/.asdf/completions/asdf.bash" ``` ::: ::: details Bash & Homebrew Adicione `asdf.sh` ao `~/.bashrc` através do comando: ```shell echo -e "\n. $(brew --prefix asdf)/asdf.sh" >> ~/.bashrc ``` O auto completar deve ser configurado seguindo as [instruções da Homebrew](https://docs.brew.sh/Shell-Completion#configuring-completions-in-bash), ou as seguintes: ```shell echo -e "\n. \"$(brew --prefix asdf)/etc/bash_completion.d/asdf.bash\"" >> ~/.bashrc ``` ::: ::: details Bash & Homebrew (macOS) Se você estiver usando o **macOS Catalina ou mais recente**, o shell padrão mudou para o **ZSH**. A não ser que você tenha voltado para o bash, siga as instruções de instalação para o ZSH. Adicione `asdf.sh` ao `~/.bash_profile` através do comando: ```shell echo -e "\n. $(brew --prefix asdf)/asdf.sh" >> ~/.bash_profile ``` O auto completar deve ser configurado seguindo as [instruções da Homebrew](https://docs.brew.sh/Shell-Completion#configuring-completions-in-bash), ou as seguintes: ```shell echo -e "\n. \"$(brew --prefix asdf)/etc/bash_completion.d/asdf.bash\"" >> ~/.bash_profile ``` ::: ::: details Bash & Pacman Adicione a seguinte linha ao seu `~/.bashrc`: ```shell . /opt/asdf-vm/asdf.sh ``` O [pacote `bash-completion`](https://wiki.archlinux.org/title/bash#Common_programs_and_options) precisa ser instalado para o auto completar funcionar. ::: ::: details Fish & Git Adicione a seguinte linha ao seu `~/.config/fish/config.fish`: ```shell source ~/.asdf/asdf.fish ``` O auto completar deve ser configurado manualmente através do seguinte comando: ```shell mkdir -p ~/.config/fish/completions; and ln -s ~/.asdf/completions/asdf.fish ~/.config/fish/completions ``` ::: ::: details Fish & Homebrew Adicione `asdf.fish` ao seu `~/.config/fish/config.fish` através do comando: ```shell echo -e "\nsource "(brew --prefix asdf)"/asdf.fish" >> ~/.config/fish/config.fish ``` O auto completar é [configurado pela Homebrew para o fish shell](https://docs.brew.sh/Shell-Completion#configuring-completions-in-fish). ::: ::: details Fish & Pacman Adicione a seguinte linha ao seu `~/.config/fish/config.fish`: ```shell source /opt/asdf-vm/asdf.fish ``` O auto completar é configurado automaticamente durante a instalação do pacote AUR. ::: ::: details Elvish & Git Adicione `asdf.elv` ao `~/.config/elvish/rc.elv` através do comando: ```shell mkdir -p ~/.config/elvish/lib; ln -s ~/.asdf/asdf.elv ~/.config/elvish/lib/asdf.elv echo "\n"'use asdf _asdf; var asdf~ = $_asdf:asdf~' >> ~/.config/elvish/rc.elv echo "\n"'set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~' >> ~/.config/elvish/rc.elv ``` Ao concluir atualizará automaticamente ::: ::: details Elvish & Homebrew Adicione `asdf.elv` ao `~/.config/elvish/rc.elv` através do comando: ```shell mkdir -p ~/.config/elvish/lib; ln -s (brew --prefix asdf)/libexec/asdf.elv ~/.config/elvish/lib/asdf.elv echo "\n"'use asdf _asdf; var asdf~ = $_asdf:asdf~' >> ~/.config/elvish/rc.elv echo "\n"'set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~' >> ~/.config/elvish/rc.elv ``` Ao concluir atualizará automaticamente ::: ::: details Elvish & Pacman Adicione `asdf.elv` ao `~/.config/elvish/rc.elv` através do comando: ```shell mkdir -p ~/.config/elvish/lib; ln -s /opt/asdf-vm/asdf.elv ~/.config/elvish/lib/asdf.elv echo "\n"'use asdf _asdf; var asdf~ = $_asdf:asdf~' >> ~/.config/elvish/rc.elv echo "\n"'set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~' >> ~/.config/elvish/rc.elv ``` Ao concluir atualizará automaticamente ::: ::: details ZSH & Git Adicione a seguinte linha ao seu `~/.zshrc`: ```shell . "$HOME/.asdf/asdf.sh" ``` **OU** utilize um framework para ZSH, como [asdf para oh-my-zsh](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/asdf) que irá adicionar o script e o auto completar. O auto completar pode ser configurado ou pelo plugin do asdf para framework para ZSH, ou através da adição das seguintes linhas ao seu `.zshrc`: ```shell # append completions to fpath fpath=(${ASDF_DIR}/completions $fpath) # initialise completions with ZSH's compinit autoload -Uz compinit && compinit ``` - Se você está utilizando uma configuração `compinit` customizada, garanta que `compinit` esteja abaixo chamada `asdf.sh` - Se você está utilizando uma configuração `compinit` customizada com um framework para ZSH, garanta que `compinit` esteja abaixo da chamada do framework. **Aviso** Se você está utilizando um framework para ZSH, o plugin do asdf pode precisar ser atualizado para utilização adequada do novo auto completar do ZSH através do `fpath`. O plugin do asdf para o oh-my-zsh ainda não foi atualizado, veja: [ohmyzsh/ohmyzsh#8837](https://github.com/ohmyzsh/ohmyzsh/pull/8837). ::: ::: details ZSH & Homebrew Adicione `asdf.sh` ao seu `~/.zshrc` através do comando: ```shell echo -e "\n. $(brew --prefix asdf)/asdf.sh" >> ${ZDOTDIR:-~}/.zshrc ``` **OU** utilize um framework para ZSH, como [asdf para oh-my-zsh](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/asdf) que irá adicionar o script e o auto completar. O auto completar pode ser configurado ou pelo framework para ZSH, ou de acordo com as [instruções da Homebrew](https://docs.brew.sh/Shell-Completion#configuring-completions-in-zsh). Se você está usando um framework para ZSH, pode ser que seja necessário atualizar o plugin do asdf para que o novo auto completar funcione adequadamente através do `fpath`. O plugin do asdf para o Oh-My-ZSH ainda será atualizado, veja: [ohmyzsh/ohmyzsh#8837](https://github.com/ohmyzsh/ohmyzsh/pull/8837). ::: ::: details ZSH & Pacman Adicione a seguinte linha ao seu `~/.zshrc`: ```shell . /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 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 "\n$env.ASDF_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 "\n$env.ASDF_DIR = (brew --prefix asdf | str trim | into string | path join 'libexec')\n source " + (brew --prefix asdf | str trim | 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 "\n$env.ASDF_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 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). ::: Os scripts do `asdf` precisam ser chamados **depois** de ter configurado a sua variável `$PATH` e **depois** de ter chamado o seu framework para ZSH (oh-my-zsh etc). Reinicie seu shell para que as mudanças na variável `PATH` tenham efeito. Abrir uma nova janela/sessão de terminal o fará. ## 4. Instalando um plugin Para demonstração, vamos instalar e configurar o [Node.js](https://nodejs.org/) através do plugin [`asdf-nodejs`](https://github.com/asdf-vm/asdf-nodejs/). ### Dependências dos plugins Cada plugin possui algumas dependências, por isso precisamos checar no repositório onde elas estão listadas. Por exemplo, para o `asdf-nodejs` são: | SO | Instalação de dependencia | | -------------- | --------------------------------------- | | Linux (Debian) | `apt-get install dirmngr gpg curl gawk` | | macOS | `brew install gpg gawk` | Devemos instalar instalar as dependências primeiro, pois alguns plugins exigem algumas ações após a instalação. ### Instalando o plugin ```shell asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git ``` ## 5. Instalando uma versão Agora temos o plugin para o Node.js, nós podemos instalar uma versão desta ferramenta. Podemos ver quais versões tão disponíveis através do comando `asdf list all nodejs`, ou uma lista específica de versões com `asdf list all nodejs 14` Vamos instalar somente a última versão disponível, utilizando a tag `latest`: ```shell asdf install nodejs latest ``` ::: tip Nota `asdf` exige versões exatas. A palavra `latest` resulta na instalação da versão atual na data da execução. ::: ## 6. Definindo uma versão `asdf` executa uma verificação das versões das ferramentas a serem utilizadas através do arquivo `.tool-versions` presente desde diretório atual, até o diretório `$HOME`. A varredura ocorre no momento em que você executa uma ferramenta que o asdf gerencia. ::: warning Se uma versão não for especificada para uma ferramenta, ao executá-la resultará em erro. `asdf current` mostrará a ferramenta e sua versão, ou então a falta dela no seu diretório atual para que você possa observar quais ferramentas falharão ao serem executadas. ::: ### Versões globais Os padrões globais são gerenciados em `$HOME/.tool-versions`. Defina uma versão global através do comando: ```shell asdf global nodejs latest ``` `$HOME/.tool-versions` ficará assim: ``` nodejs 16.5.0 ``` Alguns sistemas operacionais vêm por padrão com ferramentas que são gerenciadas pelo próprio sistema e não pelo `asdf`, `python` é um exemplo. Você precisa indicar para o `asdf` para devolver o gerenciamento para o sistema. A [seção de referência de versões](/pt-br/manage/versions.md) irá guiá-lo. ### Versões locais Versões locais são definidas no arquivo `$PWD/.tool-versions` (seu diretório atual). Geralmente, será um repositório Git para um projeto. Quando estiver no diretório desejado, execute: ```shell asdf local nodejs latest ``` `$PWD/.tool-versions` ficará assim: ``` nodejs 16.5.0 ``` ### Usando arquivos de versão existentes `asdf` suporta a migração de arquivos de versão provenientes de outros gerenciadores de versão. Por exemplo: `.ruby-version` para o `rbenv`. Essa funcionalidade é baseada no plugin de cada ferramenta. O [`asdf-nodejs`](https://github.com/asdf-vm/asdf-nodejs/) suporta tanto arquivos `.nvmrc` quanto `.node-version`. Para ativar essa funcionalidade, adicione a seguinte linha ao seu arquivo de configuração do `asdf` - `$HOME/.asdfrc`: ``` legacy_version_file = yes ``` Veja a página de refencia da [configuração](/pt-br/manage/configuration.md) para mais opções de configuração. ## Setup finalizado! A configuração inicial do `asdf` foi finalizada :tada:. Agora, você pode gerenciar versões do `nodejs` para o seus projetos. Siga passos semelhantes para cada ferramenta do seu projeto. O `asdf` possui diversos outros comandos para se acustomar ainda, você pode ver todos eles através do comando `asdf --help` ou simplesmente `asdf`. Eles estão divididos em três categorias: - [núcleo `asdf`](/pt-br/manage/core.md) - [plugins](/pt-br/manage/plugins.md) - [versões (de ferramentas)](/pt-br/manage/versions.md) ================================================ FILE: docs/pt-br/guide/introduction.md ================================================ # Introdução > Hi, we've recently migrated our docs and added some new pages. If you would like to help translate this page, see the "Edit this page" link at the bottom of the page. O `asdf` é um gerenciador de versões. Todas as definições de versão das ferramentas estão contidas no arquivo (`.tool-versions`), o qual você pode compartilhar com o seu time no repositório Git de um projeto e garantir que todos estejam usando **exatamente** a mesma versão das ferramentas. A maneira antiga de trabalhar necessitava diversos gerenciadores de versão, cada um deles com uma documentação, arquivos de configuração diferentes (manipulação do `$PATH`, shims e variáveis de ambiente, por exemplo). O `asdf` provê um único arquivo de configuração e uma única interface para simplificar o fluxo de desenvolvimento, podendo ser ampliado para todas as ferramentas através de um simples plugin. ## Funcionamento Após instalar e configurar o `asdf` no seu shell, plugins podem ser instalados para gerenciar determinadas ferramentas. Quando uma ferramenta é instalada por um plugin, os executáveis que foram instalados possuem [shims](<https://en.wikipedia.org/wiki/Shim_(computing)>) criados para cada um deles. Quando você roda algum destes executáveis, o shim é executado, permitindo que o `asdf` identifique qual versão da ferramenta está configurada no arquivo `.tool-versions` e execute esta versão. ## Projetos relacionados ### nvm / n / rbenv etc Ferramentas como o [nvm](https://github.com/nvm-sh/nvm), [n](https://github.com/tj/n) e [rbenv](https://github.com/rbenv/rbenv) são escritas em shell scripts que criam shims para os executáveis instalados para essas ferramentas. O `asdf` é bem similar e foi criado para competir neste meio de ferramentas de gerenciamento de versão. O grande diferencial do `asdf` é que seu sistema de plugins elimina a necessidade de um gerenciador de versões para cada ferramenta, esta com diferentes comandos e arquivos de configuração. <!-- ### pyenv TODO: someone with Python background expand on this `asdf` has some similarities to `pyenv` but is missing some key features. The `asdf` team is looking at introducing some of these `pyenv` specific features, though no roadmap or timeline is available. --> ### direnv > Aumenta os shells existentes com a possibilidade de utilizar diferentes variáveis de ambiente com base no diretório atual. O `asdf` não gerencia variáveis de ambiente, entretanto existe o plugin [`asdf-direnv`](https://github.com/asdf-community/asdf-direnv) para integrar o comportamento do direnv ao asdf. Veja a [documentação do direnv](https://direnv.net/) para mais detalhes. ### Homebrew > O gerenciador de pacotes faltante para o macOS (ou Linux) O Homebrew gerencia seus pacotes e dependências destes pacotes. O `asdf` não gerencia dependencias, não é um gerenciador de pacotes, a escolha do gerenciador de pacotes é reservada ao usuário. Veja a [documentação do Homebrew](https://brew.sh/) para mais detalhes. ### NixOS > O Nix é uma ferramenta que relaciona o gerenciamento de pacotes e as configurações de sistema. O NixOS visa construir ambientes verdadeiramente replicáveis através da gerência das versões exatas dos pacotes e dependências de cada ferramenta, algo que o `asdf` não faz. O NixOS faz isso através da sua própria linguagem de programação, muitas ferramentas da linha de comando e uma coleção de pacotes contendo mais de 60,000 destes. Novamente, o `asdf` não gerencia dependências/pacotes e não é um gerenciador de pacotes. Veja a [documentação do NixOS](https://nixos.org/guides/how-nix-works.html) para mais detalhes. ## Por que usar o asdf? O `asdf` garante que equipes utilizem exatamente a mesma versão de alguma ferramenta, com suporte para **diversas** delas através do sistema de plugins e a _simplicidade e familiaridade_ de ser um único **shell** script que você inclui na configuração do seu shell ::: tip Nota O `asdf` não foi feito para ser o gerenciador de pacotes do seu sistema, mas sim uma ferramenta para gerenciar versões de outras ferramentas. Não é por que é possível criar um plugin para qualquer ferramenta/linguagem com o `asdf` que esta sempre será a solução mais adequada. ::: ================================================ FILE: docs/pt-br/index.md ================================================ --- layout: home hero: name: asdf text: O Gerenciador de Múltiplas Versões de Tempo de Execução tagline: Gerencie todas as suas versões de tempo de execução com uma ferramenta! actions: - theme: brand text: Começar link: /pt-br/guide/getting-started - theme: alt text: O que é asdf? link: /pt-br/guide/introduction - theme: alt text: Ver no GitHub link: https://github.com/asdf-vm/asdf features: - title: Uma super ferramenta details: "Gerencie cada um dos runtimes e ferramentas dos seus projetos com uma única ferramenta de CLI" icon: 🎉 - title: Plugins details: "Grande ecossistema de runtimes e ferramentas existentes. API simples para adicionar suporte para novas ferramentas conforme necessário!" icon: 🔌 - title: "Compatível com vários arquivos de configuração" details: "Suporte para arquivos de configuração existentes .nvmrc, .node-version, .ruby-version para uma migração tranquila!" icon: ⏮ - title: "Só um arquivo de configuração" details: ".tool-versions para gerenciar todas as suas ferramentas, runtimes e suas versões em um único arquivo" icon: 📄 - title: "Shells" details: "Suporta Bash, ZSH, Fish & Elvish com autocomplete." icon: 🐚 - title: "GitHub Actions" details: "Fornece um GitHub Action para instalar e utilizar seu .tool-versions em seu fluxo de trabalho CICD." icon: 🤖 --- ================================================ FILE: docs/pt-br/manage/commands.md ================================================ # All Commands > Hi, we've recently migrated our docs and added some new pages. If you would like to help translate this page, see the "Edit this page" link at the bottom of the page. A lista de todos os comandos disponíveis em `asdf`. Esta lista é o texto do comando `asdf help`. <<< @../../internal/help/help.txt ================================================ FILE: docs/pt-br/manage/configuration.md ================================================ # Configuration > Hi, we've recently migrated our docs and added some new pages. If you would like to help translate this page, see the "Edit this page" link at the bottom of the page. A configuração do `asdf` abrange tanto os arquivos `.tool-versions` compartilháveis quanto as personalizações específicas do usuário com `.asdfrc` e variáveis de ambiente. ## .tool-versions Sempre que o arquivo `.tool-versions` estiver presente em um diretório, as versões da ferramenta que ele declara serão usadas nesse diretório e em seus subdiretórios. Configurações globais podem ser modificadas no arquivo `$HOME/.tool-versions` O arquivo `.tool-versions` se parece assim: ``` ruby 2.5.3 nodejs 10.15.0 ``` As versões podem estar no seguinte formato: - `10.15.0` - uma versão real. Os plugins que suportam o download de binários farão o download de binários. - `ref:v1.0.2-a` ou `ref:39cb398vb39` - _tag/commit/branch_ para download pelo github e compilação um path costumizado e compi - `path:~/src/elixir` - um path para uma versão compilada e personalizada de uma ferramenta pronta para usar. Para uso por linguagens de desenvolvimento e outros. - `system` - faz com que asdf passe para a versão da ferramenta no sistema que não é gerenciada por asdf . Várias versões podem ser definidas, separando-as com um espaço. Por exemplo, para usar Python 3.7.2, e também Python 2.7.15, use a linha abaixo em seu arquivo `.tool-versions`. ``` python 3.7.2 2.7.15 system ``` Para instalar todas as ferramentas definidas em `.tool-versions`, execute o camando `asdf install` sem argumentos no mesmo diretório de `.tool-versions`. Para isntalar somente uma ferramenta definida em `.tool-versions`, execute o camando `asdf install` sem argumentos no mesmo diretório de `.tool-versions`. A ferramenta será instalada na versão especificada no arquivo `.tool-versions`. Edite o arquivo diretamente no diretório ou use `asdf local` (ou `asdf global`) para atualiza-lo. ## `$HOME/.asdfrc` Adicione um arquivo `.asdfrc` ao seu diretório home e asdf usará as configurações especificadas no arquivo. O arquivo deve ser formatado assim: ``` legacy_version_file = yes ``` **Configurações** - `legacy_version_file` - por padrão é `no`. Se definido como `yes`, fará com que os plug-ins que suportam esse recurso leiam os arquivos de versão usados por outros gerenciadores de versão (por exemplo, `.ruby-version` no caso do `rbenv` do Ruby). - `always_keep_download` - por padrão é `no`. Se definido como `yes`, fará com que o `asdf install` sempre mantenha o código-fonte ou binário baixado. Se definido como `no`, o código fonte ou binário baixado por `asdf install` será excluído após a instalação bem sucedida. - `plugin_repository_last_check_duration` - por padrão é `60` min (1 hrs). Ele define a duração da última verificação do repositório de plugins asdf. Quando o comando `asdf plugin add <nome>`, `asdf plugin list all` for executado, ele verificará a duração da última atualização para atualizar o repositório. Se definido como `0`, ele atualizará o repositório de plugins asdf todas as vezes. ## Variáveis de ambiente - `ASDF_CONFIG_FILE` - O padrão é `~ /.asdfrc` conforme descrito acima. Pode ser definido para qualquer local. - `ASDF_TOOL_VERSIONS_FILENAME` - O nome do arquivo que armazena os nomes e versões das ferramentas. O padrão é `.tool-versions`. Pode ser qualquer nome de arquivo válido. Normalmente você não deve substituir o valor padrão, a menos que deseja que o asdf ignore os arquivos `.tool-versions`. - `ASDF_DIR` - O padrão é `~/.asdf` - Localização dos arquivos `asdf`. Se você instalar `asdf` em algum outro diretório, defina-o para esse diretório. Por exemplo, se você estiver instalando através do AUR, você deve definir isso para `/opt/asdf-vm`. - `ASDF_DATA_DIR` - O padrão é `~/.asdf` - Local onde `asdf` instala plugins, correções e instalações. Pode ser definido para qualquer local antes de fornecer `asdf.sh` ou `asdf.fish` mencionado na seção acima. Para Elvish, isso pode ser definido acima de `use asdf`. ================================================ FILE: docs/pt-br/manage/core.md ================================================ # Core > Hi, we've recently migrated our docs and added some new pages. If you would like to help translate this page, see the "Edit this page" link at the bottom of the page. A lista de comandos do núcleo `asdf` é bastante pequena, mas pode facilitar muitos fluxos de trabalho. ## Instalação e configuração Baseado no [Guia de Introdução](/pt-br/guide/getting-started.md). ## Execute ```shell asdf exec <command> [args...] ``` Executa o comando shim para a versão atual <!-- TODO: expand on this with example --> ## Variável de Ambiente ```shell asdf env <command> [util] ``` <!-- TODO: expand on this with example --> ## Informações ```shell asdf info ``` Um comando auxiliar para imprimir as informações de depuração do SO, Shell e `asdf`. Compartilhe isso ao fazer um relatório de bug. ## Reshim ```shell asdf reshim <name> <version> ``` Isso recria os shims para a versão atual de um pacote. Por padrão, os calços são criados por plugins durante a instalação de uma ferramenta. Algumas ferramentas como a [npm CLI](https://docs.npmjs.com/cli/) permitem a instalação global de executáveis, por exemplo, instalando [Yarn](https://yarnpkg.com/) via `npm install -g fio`. Como este executável não foi instalado por meio do ciclo de vida do plug-in, ainda não existe shim para ele. `asdf reshim nodejs <version>` forçará o recálculo de shims para quaisquer novos executáveis, como `yarn`, para `<version>` de `nodejs`. ## Versionamento do Shim ```shell asdf shimversions <command> ``` Lista os plugins e versões que fornecem shims para um comando. Como exemplo, o [Node.js](https://nodejs.org/) vem com dois executáveis, `node` e `npm`. Quando muitas versões das ferramentas são instaladas com [`asdf-nodejs`](https://github.com/asdf-vm/asdf-nodejs/) `shimversions` pode retornar: ```shell ➜ asdf shimversions node nodejs 14.8.0 nodejs 14.17.3 nodejs 16.5.0 ``` ```shell ➜ asdf shimversions npm nodejs 14.8.0 nodejs 14.17.3 nodejs 16.5.0 ``` ## Atualizar Por favor, use o mesmo método que você usou para instalar o asdf para atualizá-lo. A versão mais recente do asdf é mostrada no canto superior direito desta página. ## Desinstalar Para desinstalar `asdf` siga os passos: ::: details Bash & Git 1. Em seu `~/.bashrc` remova as linhas do `asdf.sh` e seus complementos: ```shell . "$HOME/.asdf/asdf.sh" . "$HOME/.asdf/completions/asdf.bash" ``` 2. Remova o diretório `$HOME/.asdf`: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 3. Execute o comando para remover todos os arquivos de configurações do `asdf`: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Bash & Git (macOS) 1. Em seu `~/.bash_profile` remova as linhas do `asdf.sh` e remova seus complementos: ```shell . "$HOME/.asdf/asdf.sh" . "$HOME/.asdf/completions/asdf.bash" ``` 2. Remova o diretório `$HOME/.asdf`: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 3. Execute o comando para remover todos os arquivos de configurações do `asdf`: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Bash & Homebrew 1. Em seu `~/.bashrc` remova as linhas do `asdf.sh` e remova seus complementos: ```shell . $(brew --prefix asdf)/libexec/asdf.sh . $(brew --prefix asdf)/etc/bash_completion.d/asdf.bash ``` ?> Os complementos precisam [instruções de configuração do Homebrew](https://docs.brew.sh/Shell-Completion#configuring-completions-in-bash) e siga o guia de remoção. 2. Desinstale usando seu gerenciador de pacotes: ```shell brew uninstall asdf --force ``` 3. Execute o comando para remover todos os arquivos de configurações do `asdf`: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Bash & Homebrew (macOS) Caso esteja usando **macOs Catalina ou mais recente**, por padrão o _shell_ é **ZSH**. Se não achar alguma configuração em seu `~/.bash_profile` talvez esteja em `~/.zshrc` em cada caso siga as intruções do ZSH. 1. Em seu `~/.bash_profile` remova as linhas do `asdf.sh` e remova seus complementos: ```shell . $(brew --prefix asdf)/libexec/asdf.sh . $(brew --prefix asdf)/etc/bash_completion.d/asdf.bash ``` ?> Os complementos precisam [instruções de configuração do Homebrew](https://docs.brew.sh/Shell-Completion#configuring-completions-in-bash) e siga o guia de remoção. 2. Desinstale usando seu gerenciador de pacotes: ```shell brew uninstall asdf --force ``` 3. Execute o comando para remover todos os arquivos de configurações do `asdf`: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Bash & Pacman 1. Em seu `~/.bashrc` remova as linhas do `asdf.sh` e seus complementos: ```shell . /opt/asdf-vm/asdf.sh ``` 2. Desinstale usando seu gerenciador de pacotes: ```shell pacman -Rs asdf-vm ``` 3. Remova o diretório `$HOME/.asdf`: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 4. Execute o comando para remover todos os arquivos de configurações do `asdf`: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Fish & Git 1. Em seu `~/.config/fish/config.fish` remova as linhas do `asdf.sh`: ```shell source ~/.asdf/asdf.fish ``` e remova os complementos de com esse comando: ```shell rm -rf ~/.config/fish/completions/asdf.fish ``` 2. Remova o diretório `$HOME/.asdf`: ```shell rm -rf (string join : -- $ASDF_DATA_DIR $HOME/.asdf) ``` 3. Execute o comando para remover todos os arquivos de configurações do `asdf`: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Fish & Homebrew 1. Em seu `~/.config/fish/config.fish` remova as linhas do `asdf.fish`: ```shell source "(brew --prefix asdf)"/libexec/asdf.fish ``` 2. Desinstale usando seu gerenciador de pacotes: ```shell brew uninstall asdf --force ``` 3. Execute o comando para remover todos os arquivos de configurações do `asdf`: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Fish & Pacman 1. Em seu `~/.config/fish/config.fish` remova as linhas do `asdf.fish`: ```shell source /opt/asdf-vm/asdf.fish ``` 2. Desinstale usando seu gerenciador de pacotes: ```shell pacman -Rs asdf-vm ``` 3. Remova o diretório `$HOME/.asdf`: ```shell rm -rf (string join : -- $ASDF_DATA_DIR $HOME/.asdf) ``` 4. Execute o comando para remover todos os arquivos de configurações do `asdf`: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Elvish & Git 1. Em seu `~/.config/elvish/rc.elv` remova as linhas que importa o módulo `asdf`: ```shell use asdf _asdf; var asdf~ = $_asdf:asdf~ set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~ ``` e desinstale o módulo `asdf` com este comando: ```shell rm -f ~/.config/elvish/lib/asdf.elv ``` 2. Remova o diretório `$HOME/.asdf`: ```shell if (!=s $E:ASDF_DATA_DIR "") { rm -rf $E:ASDF_DATA_DIR } else { rm -rf ~/.asdf } ``` 3. Execute este comando para remover todos os arquivos de configuração `asdf`: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Elvish & Homebrew 1. Em seu `~/.config/elvish/rc.elv` remova as linhas que importa o módulo `asdf`: ```shell use asdf _asdf; var asdf~ = $_asdf:asdf~ set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~ ``` e desinstale o módulo `asdf` com este comando: ```shell rm -f ~/.config/elvish/lib/asdf.elv ``` 2. Desinstale com seu gerenciador de pacotes: ```shell brew uninstall asdf --force ``` 3. Execute este comando para remover todos os arquivos de configuração `asdf`: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Elvish & Pacman 1. Em seu `~/.config/elvish/rc.elv` remova as linhas que importa o módulo `asdf`: ```shell use asdf _asdf; var asdf~ = $_asdf:asdf~ set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~ ``` e desinstale o módulo `asdf` com este comando: ```shell rm -f ~/.config/elvish/lib/asdf.elv ``` 2. Desinstale com seu gerenciador de pacotes: ```shell pacman -Rs asdf-vm ``` 3. Remova o diretório `$ HOME/.asdf`: ```shell if (!=s $E:ASDF_DATA_DIR "") { rm -rf $E:ASDF_DATA_DIR } else { rm -rf ~/.asdf } ``` 4. Execute este comando para remover todos os arquivos de configuração `asdf`: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details ZSH & Git 1. Em seu `~/.zshrc` remova as linhas do `asdf.sh` e seus complementos: ```shell . "$HOME/.asdf/asdf.sh" # ... fpath=(${ASDF_DIR}/completions $fpath) autoload -Uz compinit compinit ``` **Ou** use ZSH Framework plugin. 2. Remova o diretório `$HOME/.asdf`: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 3. Execute o comando para remover todos os arquivos de configurações do `asdf`: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details ZSH & Homebrew 1. Em seu `~/.zshrc` remova as linhas do `asdf.sh`: ```shell . $(brew --prefix asdf)/libexec/asdf.sh ``` 2. Desinstale usando seu gerenciador de pacotes: ```shell brew uninstall asdf --force ``` 3. Execute o comando para remover todos os arquivos de configurações do `asdf`: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details ZSH & Pacman 1. Em seu `~/.zshrc` remova as linhas do `asdf.sh`: ```shell . /opt/asdf-vm/asdf.sh ``` 2. Desinstale usando seu gerenciador de pacotes: ```shell pacman -Rs asdf-vm ``` 3. Remova o diretório `$HOME/.asdf`: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 4. Execute o comando para remover todos os arquivos de configurações do `asdf`: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: Tudo pronto! 🎉 ================================================ FILE: docs/pt-br/manage/plugins.md ================================================ # Plugins > Hi, we've recently migrated our docs and added some new pages. If you would like to help translate this page, see the "Edit this page" link at the bottom of the page. Plugins são como `asdf` sabe lidar com diferentes ferramentas, tais quais Node.js, Ruby, Elixir etc. Veja [Criando Plugins](/pt-br/plugins/create.md) para a API do plugin usada para suportar mais ferramentas. ## Adicionar Adicione os plugins via sua Url Git: ```shell asdf plugin add <name> <git-url> # asdf plugin add elm https://github.com/vic/asdf-elm ``` ou pelo nome abreviado dentro do repositório de plugins: ```shell asdf plugin add <name> # asdf plugin add erlang ``` ::: tip Recommendation Prefira o método mais longo `git-url`, pois ele é independente do repositório de nome abreviado. ::: ## Listar Instalados ```shell asdf plugin list # asdf plugin list # java # nodejs ``` ```shell asdf plugin list --urls # asdf plugin list # java https://github.com/halcyon/asdf-java.git # nodejs https://github.com/asdf-vm/asdf-nodejs.git ``` ## Listar todos nomes abreviados no repositório ```shell asdf plugin list all ``` Veja [Plugins Shortname Index](https://github.com/asdf-vm/asdf-plugin-template) para toda a lista de nomes curtos de plugins. ## Atualizar ```shell asdf plugin update --all ``` Se você quiser atualizar um pacote específico, apenas use. ```shell asdf plugin update <name> # asdf plugin update erlang ``` Esta atualização irá buscar o último _commit_ na _branch_ padrão no _origin_ de seu respositório. Plugins e atualizações das versões estão sendo desenvolvidas ([#916](https://github.com/asdf-vm/asdf/pull/916)) ## Remover ```bash asdf plugin remove <name> # asdf plugin remove erlang ``` Removendo o plugin irá remover todas as instalações feitas com o plugin. Isso pode ser usado como um atalho para apagar/remover sujeiras de versões não utilizadas de uma ferramenta. ## Sincronizar nome abreviado no repositório O nome abreviado do repositório é sincronizado em seu máquina local e periodicamente atualizado. Esse período pode ser determinado com o seguinte método: - comandos `asdf plugin add <name>` ou `asdf plugin list all` disparam a sincronização - ocorre uma sincronização se não houver nenhuma nos últimos `X` minutos - `X` por padrão é `60`, mas pode ser mudado em `.asdfrc` via as opções do `plugin_repository_last_check_duration`. Seja mais em [asdf documentação de configuração](/pt-br/manage/configuration.md). ================================================ FILE: docs/pt-br/manage/versions.md ================================================ # Versões > Hi, we've recently migrated our docs and added some new pages. If you would like to help translate this page, see the "Edit this page" link at the bottom of the page. ## Instalar Versão ```shell asdf install <name> <version> # asdf install erlang 17.3 ``` Se um plugin suporta o download e compilação do código-fonte, você pode especificar `ref:foo` no qual `foo` é uma 'branch' especifica, 'tag', ou 'commit'. Você também precisará usar o mesmo nome e referência ao desinstalar. ## Instalar última versão estável ```shell asdf install <name> latest # asdf install erlang latest ``` Instale a última versão estável que inicia com um texto. ```shell asdf install <name> latest:<version> # asdf install erlang latest:17 ``` ## Listar versões instaladas ```shell asdf list <name> # asdf list erlang ``` Limite as versões que inicie com um determinado texto. ```shell asdf list <name> <version> # asdf list erlang 17 ``` ## Listar todas as versões disponíveis ```shell asdf list all <name> # asdf list all erlang ``` Limite as versões que inicie com um determinado texto. ```shell asdf list all <name> <version> # asdf list all erlang 17 ``` ## Mostrar última versão estável ```shell asdf latest <name> # asdf latest erlang ``` Mostrar última versão estável que inicie com um determinado texto. ```shell asdf latest <name> <version> # asdf latest erlang 17 ``` ## Selecionar versão atual ```shell asdf global <name> <version> [<version>...] asdf shell <name> <version> [<version>...] asdf local <name> <version> [<version>...] # asdf global elixir 1.2.4 asdf global <name> latest[:<version>] asdf local <name> latest[:<version>] # asdf global elixir latest ``` `global` escreve a versão para `$HOME/.tool-versions`. `shell` selecione a versão na variável de ambiente `ASDF_${LANG}_VERSION`, para a atual seção do _shell_. `local` escreve a versão para `$PWD/.tool-versions`, crie se necessário . Veja em `.tool-versions` [arquivo de seleção de configuração](/pt-br/manage/configuration) para mais detalhes. ::: warning Alternativa Se você quiser selecionar a versão atual do seu _shell_ ou para executar um comando em uma versão específica de sua ferramenta, você pode selecionar a versão na variável de ambiente `ASDF_${TOOL}_VERSION`. ::: O seguinte exemplo executa os testes em um projeto Elixir na versão `1.4.0`. O formato da versão é o mesmo suportado pelo arquivo `.tool-versions`. ```shell ASDF_ELIXIR_VERSION=1.4.0 mix test ``` ## Resposta do sistema de versão Para usar o sistema de versão da ferramenta `<name>` inicie um gerenciador de versões do asdf para selecionar a versão na ferramenta do `system`. Selecione o sistema com `global`, `local` ou `shell` Set system with either `global`, `local` or `shell` conforme descrito em [Selecionar versão atual](#selecionar-versão-atual). ```shell asdf local <name> system # asdf local python system ``` ## Verificar a versão atual ```shell asdf current # asdf current # erlang 17.3 (set by /Users/kim/.tool-versions) # nodejs 6.11.5 (set by /Users/kim/cool-node-project/.tool-versions) asdf current <name> # asdf current erlang # 17.3 (set by /Users/kim/.tool-versions) ``` ## Desinstalar versão ```shell asdf uninstall <name> <version> # asdf uninstall erlang 17.3 ``` ## Shims Quando asdf instala um pacote é criado _shims_ para cada programa executado no pacote do diretório `$ASDF_DATA_DIR/shims` (padrão `~/.asdf/shims`). Esse diretório começa no `$PATH` (pelos `asdf.sh`, `asdf.fish`, etc) é como o programa instalado é disponibilizado no ambiente do sistema. Os _shims_ em si são atalhos simples que executam um programa auxiliar `asdf exec` passando o nome do plugin e o caminho para o executável no pacote instalado que o _shim_ está contido. O `asdf exec` ajuda a determinar a versão do pacote usado (como especificado no arquivo `.tool-versions`, pelo `asdf local ...` ou `asdf global ...`), o final do _path_ do executavél no pacote instalado no diretório (pode ser manipulado pelo `exec-path` no _callback_ do plugin) e o ambiente executado em (também fornecido pelo plugin - `exec-env`) e finalmente executado. ::: warning Observação Observe que, como este sistema usa chamadas `exec`, qualquer _scripts_ no pacote devem ser fornecidos pelo _shell_, a instancia em execução precisa ser aessado diretamente ao invés do _shim_. Os dois comandos do asdf: `which` e `where` pode ajudar com o retorno do caminho para o pacote instalado: ::: ```shell # retorna o 'path' da versão atual em execução source $(asdf which ${PLUGIN})/../script.sh # retorna o 'path' do pacote instalado no diretório source $(asdf where ${PLUGIN} $(asdf current ${PLUGIN}))/bin/script.sh ``` ### Ignorando _shims_ do asdf Se por algum motivo você deseja ignorar _shims_ do asdf ou deseja que suas variáveis de ambiente sejam definidas automaticamente ao entrar no diretório do seu projeto, pode ser útil o [asdf-direnv](https://github.com/asdf-community/asdf-direnv). Verifique o README para mais detalhes. ================================================ FILE: docs/pt-br/more/community-projects.md ================================================ # Community Projects Here are some community projects related to `asdf`: - [asdf-community](https://github.com/asdf-community): A collaborative, community-driven project for long-term maintenance of asdf plugins. - [asdf dev container](https://github.com/iloveitaly/asdf-devcontainer): A [GitHub Dev Container](https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/introduction-to-dev-containers) supporting asdf managed tools in GitHub Codespaces. ::: warning Note asdf core do not own these projects or their code. asdf core are not responsible for the quality or security as they relate to those listed here. ::: ================================================ FILE: docs/pt-br/more/faq.md ================================================ # Perguntas frequentes Aqui estão algumas perguntas comuns sobre `asdf`. ## Suporte WSL1? WSL1 ([Windows Subsystem for Linux](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux) 1) não é oficialmente suportado. Alguns aspectos do `asdf` podem não funcionar corretamente. Não temos a intenção de adicionar suporte oficial para WSL1. ## Suporte WSL2? WSL2 ([Subsistema Windows para Linux](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux#WSL_2) 2) deve funcionar usando as instruções de configuração e dependência para a distribuição WSL escolhida. É importante ressaltar que o WSL2 _apenas_ deve funcionar corretamente quando o diretório de trabalho atual é uma unidade Unix e não uma unidade Windows vinculada. Pretendemos executar o conjunto de testes no WSL2 quando o suporte ao host runner estiver disponível nas Ações do GitHub; atualmente, esse não parece ser o caso. ## Exectable recém-instalado não está funcionando? > Acabei de instalar o `npm -g yarn`, mas não consigo executar o `yarn`. O que da? `asdf` usa [shims](<https://en.wikipedia.org/wiki/Shim_(computing)>) para gerenciar executáveis. Aqueles instalados por plug-ins têm shims criados automaticamente, enquanto a instalação de executáveis ​​por meio de uma ferramenta gerenciada `asdf` exigirá que você notifique o`asdf` sobre a necessidade de criar shims. Neste caso, para criar um shim para [Yarn](https://yarnpkg.com/). Veja a documentação do comando [`asdf reshim`](/ manage / core.md # reshim). ## Shell não detecta shims recém-instalados? Se `asdf reshim` não está resolvendo seu problema, então é mais provável devido ao sourcing de`asdf.sh` ou `asdf.fish` _não_ estar no ** BOTTOM ** de seu arquivo de configuração Shell (`.bash_profile`, `.zshrc`, `config.fish`, etc). Ele precisa ser fornecido **DEPOIS** de você definir seu `$PATH` e **DEPOIS** de ter fornecido seu framework (oh-meu-zsh etc), se houver. ================================================ FILE: docs/pt-br/more/thanks.md ================================================ # Créditos Eu ([@HashNuke](https://github.com/HashNuke)), febre alta, resfriado e tosse. Copyright 2014 até o final dos tempos ([MIT License](https://github.com/asdf-vm/asdf/blob/master/LICENSE)) ## Mantenedores - [@HashNuke](https://github.com/HashNuke) - [@danhper](https://github.com/danhper) - [@Stratus3D](https://github.com/Stratus3D) - [@vic](https://github.com/vic) - [@jthegedus](https://github.com/jthegedus) ## Contribuidores Veja em [lista de contribuidores](https://github.com/asdf-vm/asdf/graphs/contributors) :pray: no GitHub ================================================ FILE: docs/pt-br/plugins/create.md ================================================ # Criar um plug-in > Hi, we've recently migrated our docs and added some new pages. If you would like to help translate this page, see the "Edit this page" link at the bottom of the page. ## O que há em um plug-in Um plugin é um repositório git, com alguns scripts executáveis, para dar suporte ao versionamento de outra linguagem ou ferramenta. Esses scripts são executados quando os comandos `list-all`, `install` ou `uninstall` são executados. Você pode definir ou desmarcar env vars e fazer qualquer coisa necessária para configurar o ambiente para a ferramenta. ## Scripts obrigatórios - `bin/list-all` - lista todas as versões instaláveis - `bin/download` - baixe o código fonte ou binário para a versão especificada - `bin/install` - instala a versão especificada ## Variavéis de Ambiente Todos os scripts, exceto `bin/list-all`, terão acesso aos seguintes env vars para agir: - `ASDF_INSTALL_TYPE` - `version` ou `ref` - `ASDF_INSTALL_VERSION` - se `ASDF_INSTALL_TYPE` é `version` então este será o número da versão. Caso contrário, será o git ref que será passado. Pode apontar para uma tag/commit/branch no repositório. - `ASDF_INSTALL_PATH` - o diretório onde _foi_ instalado (ou _deve_ ser instalado no caso do script `bin/install`) Essas variáveis de ambiente adicionais estarão disponíveis para o script `bin/install`: - `ASDF_CONCURRENCY` - o número de núcleos a serem usados ao compilar o código-fonte. Útil para definir `make -j`. - `ASDF_DOWNLOAD_PATH` - o caminho para onde o código fonte ou binário foi baixado pelo script `bin/download`. Essas variáveis de ambiente adicionais estarão disponíveis para o script `bin/download`: - `ASDF_DOWNLOAD_PATH` - o caminho para onde o código-fonte ou binário deve ser baixado. #### bin/list-all Deve imprimir uma string com uma lista de versões separadas por espaço. A saída de exemplo seria a seguinte: ```shell 1.0.1 1.0.2 1.3.0 1.4 ``` Observe que a versão mais recente deve ser listada por último para que apareça mais próxima do prompt do usuário. Isso é útil já que o comando `list-all` imprime cada versão em sua própria linha. Se houver muitas versões, é possível que as primeiras versões fiquem fora da tela. Se as versões estiverem sendo extraídas da página de lançamentos em um site, é recomendável não classificar as versões, se possível. Muitas vezes as versões já estão na ordem correta ou, na ordem inversa, nesse caso algo como `tac` deve ser suficiente. Se você precisar classificar as versões manualmente, não poderá confiar em `sort -V`, pois não é suportado no OSX. Uma função de classificação alternativa [como esta é uma escolha melhor](https://github.com/vic/asdf-idris/blob/master/bin/list-all#L6). #### bin/download Este script deve baixar o código fonte ou binário, no caminho contido na variável de ambiente `ASDF_DOWNLOAD_PATH`. Se o código-fonte ou binário baixado estiver compactado, apenas o código-fonte ou binário descompactado poderá ser colocado no diretório `ASDF_DOWNLOAD_PATH`. O script deve sair com um status de `0` quando o download for bem-sucedido. Se o download falhar, o script deve sair com qualquer status de saída diferente de zero. Se possível, o script deve apenas colocar arquivos no `ASDF_DOWNLOAD_PATH`. Se o download falhar, nenhum arquivo deve ser colocado no diretório. Se este script não estiver presente, o asdf assumirá que o script `bin/install` está presente e fará o download e instalará a versão. asdf só funciona sem este script para suportar plugins legados. Todos os plugins devem incluir este script e, eventualmente, o suporte para plugins legados será removido. #### bin/install Este script deve instalar a versão, no caminho mencionado em `ASDF_INSTALL_PATH`. Por padrão, o asdf criará shims para qualquer arquivo em `$ASDF_INSTALL_PATH/bin` (isso pode ser personalizado com o script opcional [bin/list-bin-paths](#binlist-bin-paths)). O script de instalação deve sair com um status de `0` quando a instalação for bem-sucedida. Se a instalação falhar, o script deve sair com qualquer status de saída diferente de zero. Se possível, o script deve apenas colocar os arquivos no diretório `ASDF_INSTALL_PATH` uma vez que a compilação e instalação da ferramenta são consideradas bem sucedidas pelo script de instalação. asdf [verifica a existência](https://github.com/asdf-vm/asdf/blob/242d132afbf710fe3c7ec23c68cec7bdd2c78ab5/lib/utils.sh#L44) do diretório `ASDF_INSTALL_PATH` para determinar se essa versão da ferramenta está instalado. Se o diretório `ASDF_INSTALL_PATH` for preenchido no início do processo de instalação, outros comandos asdf executados em outros terminais durante a instalação podem considerar essa versão da ferramenta instalada, mesmo quando não estiver totalmente instalada. Se você quiser que seu plugin funcione com asdf versão 0.7._ e anterior e versão 0.8._ e mais recente, verifique a presença da variável de ambiente `ASDF_DOWNLOAD_PATH`. Se não estiver definido, baixe o código-fonte no retorno de chamada bin/install. Se estiver definido, suponha que o script `bin/download` já tenha baixado. ## Scripts Opcional #### scripts bin/help Este não é um script de retorno de chamada, mas sim um conjunto de scripts de retorno de chamada que imprimem documentação diferente para STDOUT. Os scripts de retorno de chamada possíveis estão listados abaixo. Observe que `bin/help.overview` é um caso especial, pois deve estar presente para que qualquer saída de ajuda seja exibida para o script. - `bin/help.overview` - Este script deve gerar uma descrição geral sobre o plugin e a ferramenta que está sendo gerenciada. Nenhum título deve ser impresso, pois o asdf imprimirá títulos. A saída pode ser um texto de formato livre, mas idealmente apenas um parágrafo curto. Este script deve estar presente se você quiser que o asdf forneça informações de ajuda para seu plugin. Todos os outros scripts de retorno de chamada de ajuda são opcionais. - `bin/help.deps` - Esse script deve gerar a lista de dependências adaptadas ao sistema operacional. Uma dependência por linha. - `bin/help.config` - Este script deve imprimir qualquer configuração obrigatória ou opcional que possa estar disponível para o plug-in e a ferramenta. Quaisquer variáveis de ambiente ou outros sinalizadores necessários para instalar ou compilar a ferramenta (para o sistema operacional dos usuários quando possível). A saída pode ser texto de formato livre. - `bin/help.links` - Esta deve ser uma lista de links relevantes para o plug-in e a ferramenta (mais uma vez, adaptados ao sistema operacional atual, quando possível). Um link por linha. As linhas podem estar no formato `<title>: <link>` ou apenas `<link>`. Cada um desses scripts deve adaptar sua saída ao sistema operacional atual. Por exemplo, quando no Ubuntu, o script deps pode gerar as dependências como pacotes apt-get que devem ser instalados. O script também deve adaptar sua saída ao valor de `ASDF_INSTALL_VERSION` e `ASDF_INSTALL_TYPE` quando as variáveis forem definidas. Eles são opcionais e nem sempre serão definidos. O script de retorno de chamada de ajuda NÃO DEVE gerar nenhuma informação que já esteja coberta na documentação principal do asdf-vm. As informações gerais de uso do asdf não devem estar presentes. #### bin/list-bin-paths Liste os executáveis para a versão especificada da ferramenta. Deve imprimir uma string com uma lista separada por espaços de caminhos de diretórios que contêm executáveis. Os caminhos devem ser relativos ao caminho de instalação passado. A saída de exemplo seria: ```shell bin tools veggies ``` Isso instruirá o asdf a criar shims para os arquivos em `<install-path>/bin`, `<install-path>/tools` e `<install-path>/veggies` Se este script não for especificado, o asdf procurará o diretório `bin` em uma instalação e criará shims para eles. #### bin/exec-env Configure o env para executar os binários no pacote. #### bin/exec-path Obtenha o caminho executável para a versão especificada da ferramenta. Deve imprimir uma string com o caminho executável relativo. Isso permite que o plug-in substitua condicionalmente o caminho executável especificado do shim, caso contrário, retorne o caminho padrão especificado pelo shim. ```shell Usage: plugin/bin/exec-path <install-path> <command> <executable-path> Example Call: ~/.asdf/plugins/foo/bin/exec-path "~/.asdf/installs/foo/1.0" "foo" "bin/foo" Output: bin/foox ``` #### bin/uninstall Desinstala uma versão específica de uma ferramenta. #### bin/list-legacy-filenames Registre arquivos setter adicionais para este plugin. Deve imprimir uma string com uma lista de nomes de arquivos separados por espaços. ```shell .ruby-version .rvmrc ``` Nota: Isso só se aplica a usuários que habilitaram a opção `legacy_version_file` em seu `~/.asdfrc`. #### bin/parse-legacy-file Isso pode ser usado para analisar ainda mais o arquivo legado encontrado pelo asdf. Se o `parse-legacy-file` não for implementado, o asdf simplesmente irá cat o arquivo para determinar a versão. O script receberá o caminho do arquivo como seu primeiro argumento. #### bin/post-plugin-add Isso pode ser usado para executar qualquer ação pós-instalação depois que o plug-in for adicionado ao asdf. O script tem acesso ao caminho em que o plugin foi instalado (`${ASDF_PLUGIN_PATH}`) e o URL de origem (`${ASDF_PLUGIN_SOURCE_URL}`), se algum foi usado. Veja também os ganchos relacionados: - `pre_asdf_plugin_add` - `pre_asdf_plugin_add_${plugin_name}` - `post_asdf_plugin_add` - `post_asdf_plugin_add_${plugin_name}` #### bin/pre-plugin-remove Isso pode ser usado para executar qualquer ação de pré-remoção antes que o plug-in seja removido do asdf. O script tem acesso ao caminho em que o plugin foi instalado (`${ASDF_PLUGIN_PATH}`). Veja também os ganchos relacionados: - `pre_asdf_plugin_remove` - `pre_asdf_plugin_remove_${plugin_name}` - `post_asdf_plugin_remove` - `post_asdf_plugin_remove_${plugin_name}` ## Comandos de extensão para asdf CLI. É possível que plugins definam novos comandos asdf fornecendo scripts ou executáveis `lib/commands/command*.bash` que será chamado usando a interface de linha de comando asdf usando o nome do plug-in como um subcomando. Por exemplo, suponha que um plugin `foo` tenha: ```shell foo/ lib/commands/ command.bash command-bat.bash command-bat-man.bash command-help.bash ``` Os usuários agora podem executar ```shell $ 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` $ asdf foo bat man # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-bat-man.bash` $ asdf foo bat baz # same as running `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-bat.bash baz` ``` Os autores de plugins podem usar esse recurso para fornecer utilitários relacionados às suas ferramentas, ou até mesmo criar plugins que são apenas novas extensões de comando para o próprio asdf. Quando invocados, se os comandos de extensão não tiverem seus bits executáveis definidos, eles serão originado como scripts bash, tendo todas as funções de `$ASDF_DIR/lib/utils.bash` disponíveis. Além disso, o `$ASDF_CMD_FILE` resolve para o caminho completo do arquivo que está sendo originado. Se o bit executável estiver definido, eles são apenas executados e substituem a execução do asdf. Um bom exemplo desse recurso é para plugins como [`haxe`](https://github.com/asdf-community/asdf-haxe) que fornece o `asdf haxe neko-dylibs-link` para corrigir um problema onde os executáveis haxe esperam encontrar bibliotecas dinâmicas relativas ao diretório executável. Se o seu plug-in fornecer um comando de extensão asdf, certifique-se de mencioná-lo no README do seu plug-in. ## Modelos de calços personalizados **POR FAVOR, use este recurso apenas se for absolutamente necessário** asdf permite modelos de calços personalizados. Para um executável chamado `foo`, se houver um arquivo `shims/foo` no plug-in, o asdf copiará esse arquivo em vez de usar seu modelo padrão de shim. Isso deve ser usado com sabedoria. Por enquanto AFAIK, está sendo usado apenas no plugin Elixir, porque um executável também é lido como um arquivo Elixir, além de ser apenas um executável. O que torna impossível usar o calço bash padrão. ## Testando plug-ins `asdf` contém o comando `plugin-test` para testar seu plugin. Você pode usá-lo da seguinte forma ```shell asdf plugin test <plugin-name> <plugin-url> [--asdf-tool-version <version>] [--asdf-plugin-gitref <git-ref>] [test-command*] ``` Apenas os dois primeiros argumentos são necessários. Se \__version_ for especificado, a ferramenta será instalada com essa versão específica. O padrão é o que retorna `asdf latest <plugin-name>`. Se _git-ref_ for especificado, o plug-in em si é verificado nesse commit/branch/tag, útil para testar um pull-request no CI do seu plug-in. O padrão é o branch _default_ do repositório do plugin. Os argumentos Rest são considerados o comando a ser executado para garantir que a ferramenta instalada funcione corretamente. Normalmente seria algo que leva `--version` ou `--help`. Por exemplo, para testar o plugin NodeJS, podemos executar ```shell asdf plugin test nodejs https://github.com/asdf-vm/asdf-nodejs.git node --version ``` É altamente recomendável que você teste seu plug-in em um ambiente CI e verifique se ele funciona no Linux e no OSX. #### Exemplo GitHub Action O repositório [asdf-vm/actions](https://github.com/asdf-vm/actions) fornece uma ação do GitHub para testar seus plugins hospedados no github. ```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 ``` #### Exemplo de configuração do TravisCI Aqui está um arquivo `.travis.yml` de amostra, personalize-o de acordo com suas necessidades ```yaml language: c script: asdf plugin test nodejs $TRAVIS_BUILD_DIR 'node --version' before_script: - git clone https://github.com/asdf-vm/asdf.git asdf - . asdf/asdf.sh os: - linux - osx ``` Notas: Ao usar outro IC, você precisará verificar qual variável mapeia para o caminho do repositório. Você também tem a opção de passar um caminho relativo para `plugin-test`. Por exemplo, se o script de teste for executado no diretório: `asdf plugin test nodejs . 'node --version'`. ## Limitação de taxa da API do GitHub Se o `list-all` do seu plug-in depender do acesso à API do GitHub, certifique-se de fornecer um token de autorização ao acessá-lo, caso contrário, seus testes podem falhar devido à limitação de taxa. Para fazer isso, crie um [novo token pessoal](https://github.com/settings/tokens/new) com apenas acesso `public_repo`. Em seguida, nas configurações de compilação do travis.ci, adicione uma variável de ambiente _secure_ para ela chamada algo como `GITHUB_API_TOKEN`. E _NUNCA_ publique seu token em seu código. Finalmente, adicione algo como o seguinte para `bin/list-all` ```shell cmd="curl -s" if [ -n "$GITHUB_API_TOKEN" ]; then cmd="$cmd -H 'Authorization: token $GITHUB_API_TOKEN'" fi cmd="$cmd $releases_path" ``` ## Enviando plugins para o repositório oficial de plugins `asdf` pode facilmente instalar plugins especificando o url do repositório de plugins, por exemplo. `plugin add my-plugin https://github.com/user/asdf-my-plugin.git`. Para facilitar para seus usuários, você pode adicionar seu plugin ao repositório oficial de plugins para ter seu plugin listado e facilmente instalável usando um comando mais curto, por exemplo `asdf plugin add my-plugin`. Siga as instruções no repositório de plugins: [asdf-vm/asdf-plugins](https://github.com/asdf-vm/asdf-plugins). ================================================ FILE: docs/zh-hans/contribute/core.md ================================================ # asdf `asdf` 核心贡献指南. ## 初始化安装 在 Github 上 fork `asdf` 并且/或者使用 Git 克隆默认分支: ```shell # 克隆你 fork 的 asdf git clone https://github.com/<GITHUB_USER>/asdf.git # 或者直接克隆 asdf git clone https://github.com/asdf-vm/asdf.git ``` 核心开发所需的工具都列举在这个存储库的 `.tool-versions` 文件中。如果你想要使用 `asdf` 自身来管理它,请使用以下命令添加这些插件: ```shell asdf plugin add bats https://github.com/timgluz/asdf-bats.git asdf plugin add shellcheck https://github.com/luizm/asdf-shellcheck.git asdf plugin add shfmt https://github.com/luizm/asdf-shfmt.git ``` 使用以下命令安装这些版本来开发 `asdf`: ```shell asdf install ``` 在本地机器的开发过程中不使用 `asdf` 来管理工具 _或许_ 对你有帮助,因为你可能需要打破某些可能会影响到你的开发工具链的功能。以下是所需工具的原始列表: - [bats-core](https://github.com/bats-core/bats-core):Bash 自动化测试系统,用于单元测试 Bash 或 POSIX 兼容脚本。 - [shellcheck](https://github.com/koalaman/shellcheck):Shell 脚本的静态分析工具。 - [shfmt](https://github.com/mvdan/sh):支持 Bash 的 Shell 解析器、格式化器和解释器;包含 shfmt。 ## 开发 如果你想要在不更改已安装的 `asdf` 的情况下尝试应用你的更改,可以将 `$ASDF_DIR` 变量设置为克隆存储库的路径,并临时将目录的 `bin` 和 `shims` 目录添加到你的路径中。 最好在提交或推送到远程之前,在本地做好格式化、lint 检查和测试你的代码。可以使用以下脚本/命令: ```shell # 脚本检查 ./scripts/lint.bash --check # 格式化 ./scripts/lint.bash --fix # 测试:所有案例 ./scripts/test.bash # 测试:特定命令 bats test/list_commands.bash ``` ::: tip 提示 **增加测试!** - 新特性需要进行测试,并加快错误修复的审查速度。请在创建拉取请求之前覆盖新的代码路径。查看 [bats-core 文档](https://bats-core.readthedocs.io/en/stable/index.html) 了解更多。 ::: ### Gitignore 以下是 `asdf-vm/asdf` 仓库中的 `.gitignore` 文件。我们忽略项目特定的文件。与操作系统、工具或工作流程相关的文件应在全 `.gitignore` 配置中忽略, [请查看此处](http://stratus3d.com/blog/2018/06/03/stop-excluding-editor-temp-files-in-gitignore/) 了解更多。 <<< @/../.gitignore ### `.git-blame-ignore-revs` `asdf` 使用 `.git-blame-ignore-revs` 文件来减少在运行 blame 命令时的噪音。请查看 [git blame documentation](https://git-scm.com/docs/git-blame) 了解更多。 使用 `git blame` 时,可通过以下方式使用该文件: ```sh git blame --ignore-revs-file .git-blame-ignore-revs ./test/install_command.bats ``` 可选地,配置为在每次调用 `blame` 时自动使用该文件,无需手动提供: ```sh git config blame.ignoreRevsFile .git-blame-ignore-revs ``` 可以配置集成开发环境(IDEs)使用该文件。例如,在使用 VSCode (搭配 [GitLens](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens)) 时,将以下内容写入 `.vscode/settings.json`: ```json { "gitlens.advanced.blame.customArguments": [ "--ignore-revs-file", ".git-blame-ignore-revs" ] } ``` ## Bats 测试 在本地执行测试: ```shell ./scripts/test.bash ``` 在编写测试之前,**请务必阅读**: - `test/` 目录中的现有测试 - [bats-core 文档](https://bats-core.readthedocs.io/en/stable/index.html) - `scripts/test.bash` 中使用的现有 Bats 设置 ### Bats 提示 Bats 调试有时可能很困难。使用带有 `-t` 标识的 TAP 输出将使你能够在测试执行期间打印带有特殊文件描述符 `>&3` 的输出,从而简化调试。例如: ```shell # test/some_tests.bats printf "%s\n" "Will not be printed during bats test/some_tests.bats" printf "%s\n" "Will be printed during bats -t test/some_tests.bats" >&3 ``` 进一步相关文档请查看 bats-core 的 [终端打印](https://bats-core.readthedocs.io/en/stable/writing-tests.html#printing-to-the-terminal) 部分. ## 拉取请求、发布以及约定式提交 `asdf` 正在使用一个名为 [Release Please](https://github.com/googleapis/release-please) 的自动发布工具来自动碰撞 [SemVer](https://semver.org/) 版本并生成 [变更日志](https://github.com/asdf-vm/asdf/blob/master/CHANGELOG.md)。这个信息是通过读取自上次发布以来的提交历史记录来确定的。 [约定式提交](https://www.conventionalcommits.org/zh-hans/) 定义了拉取请求标题的格式,该标题成为默认分支上的提交消息格式。这是通过 Github Action [`amannn/action-semantic-pull-request`](https://github.com/amannn/action-semantic-pull-request) 强制执行的。 约定式提交遵循以下格式: ``` <type>[optional scope][optional !]: <description> <!-- 例子 --> fix: some fix feat: a new feature docs: some documentation update docs(website): some change for the website feat!: feature with breaking change ``` `<types>` 的所有类型包含: `feat`、`fix`、`docs`、`style`、`refactor`、`perf`、`test`、`build`、`ci`、`chore`、`revert`。 - `!`:表示重大更改 - `fix`:将会创建一个新的 SemVer `patch` 补丁 - `feat`:将会创建一个新的 SemVer `minor` 小版本 - `<type>!`:将会创建一个新的 SemVer `major` 大版本 拉取请求标题必须遵循这种格式。 ::: tip 提示 请使用约定式提交信息格式作为拉取请求标题。 ::: ## Docker 镜像 [asdf-alpine](https://github.com/vic/asdf-alpine) 和 [asdf-ubuntu](https://github.com/vic/asdf-ubuntu) 项目正在努力提供一些 asdf 工具的容器化镜像。你可以使用这些容器镜像作为开发服务器的基础镜像,或者运行生产应用。 ================================================ FILE: docs/zh-hans/contribute/documentation.md ================================================ # 文档 & 网站 文档 & 网站贡献指南。 ## 初始化设置 在 Github 上 fork `asdf` 并且/或者使用 Git 克隆默认分支: ```shell # 克隆你 fork 的 asdf git clone https://github.com/<GITHUB_USER>/asdf.git # 或者直接克隆 asdf git clone https://github.com/asdf-vm/asdf.git ``` 文档网站开发所需的工具都在文件 `docs/.tool-versions` 中使用 `asdf` 进行管理。使用以下命令添加插件: ```shell asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs ``` 使用以下命令安装工具版本: ```shell asdf install ``` - [Node.js](https://nodejs.org/zh-cn/):基于 Chrome 的 V8 引擎的 JavaScript 运行环境。 根据 `docs/package.json` 文件安装 Node.js 依赖: ```shell npm install ``` ## 开发 [Vitepress (v2)](https://vitepress.dev/zh/) 是我们用来构建 asdf 文档网站的静态站点生成器(SSG)。它被选中来取代 [Docsify.js](https://docsify.js.org/#/zh-cn/),因为我们希望在用户没有可用或未启用 JavaScript 时支持仅依靠 HTML。Docsify 无法做到这一点。除此之外,两者特性集合大致相同,重点是 Vuepress 可以用最少的配置编写 Markdown 文件。 `package.json` 包含了开发所需的脚本: @[code json{3-5}](../../package.json) 启动本地开发服务器: ```shell npm run dev ``` 在提交之前格式化代码: ```shell npm run format ``` ## 拉取请求、发布以及约定式提交 `asdf` 正在使用依赖 PR 标题中的约定式提交的自动化发布流水线。具体的文档可以查看 [核心贡献指南](./core.md). 当为文档更改创建 PR 请求时,请确保 PR 标题使用了约定式提交类型 `docs` 以及 `docs: <description>` 的格式。 ## Vitepress 网站的配置包含在几个 JavaScript 文件中,其中 JS 对象用于表示配置。它们是: - `docs/.vitepress/config.js`:网站的根配置文件。请查看 [Vitepress 文档](https://vitepress.dev/zh/reference/site-config) 了解更多详情。 为了简化根配置文件,更大的 JS 对象表示 _导航栏和侧边栏_ 配置已经被提取并按照语言类型分隔开来。请参考以下文件: - `docs/.vitepress/navbar.js` - `docs/.vitepress/sidebar.js` 这些配置的官方文档位于 [默认主题参考](https://vitepress.dev/zh/reference/default-theme-config)。 ## I18n 国际化 Vitepress 有一流的国际化支持。根配置文件 `docs/.vitepress/config.js` 定义了支持的语言类型及其 URL、在选择下拉菜单中的标题以及导航栏/侧边栏配置引用。 导航栏/侧边栏配置在上述配置文件中捕获,按语言类型分隔开并单独导出。 每种语言的 markdown 内容必须位于与根配置文件中 `locale` 键同名的目录位置。也就是: ```js // docs/.vitepress/config.js export default defineConfig({ ... locales: { root: { label: "English", lang: "en-US", themeConfig: { nav: navbars.en, sidebar: sidebars.en, }, }, "pt-br": { label: "Brazilian Portuguese", lang: "pr-br", themeConfig: { nav: navbars.pt_br, sidebar: sidebars.pt_br, }, }, "zh-hans": { label: "简体中文", lang: "zh-hans", themeConfig: { nav: navbars.zh_hans, sidebar: sidebars.zh_hans, }, }, }, }) ``` `/pt-BR/` 将要求 markdown 文件的同一集合位于 `docs/pt-BR/` 目录下,如下所示: ```shell docs ├─ README.md ├─ foo.md ├─ nested │ └─ README.md └─ pt-BR ├─ README.md ├─ foo.md └─ nested └─ README.md ``` 请查看 [Vitepress i18n 国际化官方文档](https://vitepress.dev/zh/guide/i18n) 了解更多详情。 ================================================ FILE: docs/zh-hans/contribute/first-party-plugins.md ================================================ # 官方插件 asdf 核心团队已经开发了一些与他们日常工作相关的插件。随时欢迎大家维护和改进这些插件。这些插件所对应的存储库链接如下所示: - [Elixir](https://github.com/asdf-vm/asdf-elixir) - [Erlang](https://github.com/asdf-vm/asdf-erlang) - [Node.js](https://github.com/asdf-vm/asdf-nodejs) - [Ruby](https://github.com/asdf-vm/asdf-ruby) 对于社区插件,请参考: - [`asdf-community` 组织](https://github.com/asdf-community):一个用于长期维护 `asdf` 插件的协作、社区驱动的项目。 - [`asdf-plugins` 缩写存储库](https://github.com/asdf-vm/asdf-plugins):`asdf` 核心用于查找流行的 `asdf` 插件的缩写列表。 - [GitHub `asdf-plugin` 主题搜索](https://github.com/topics/asdf-plugin) ================================================ FILE: docs/zh-hans/contribute/github-actions.md ================================================ # GitHub Actions 感谢你的关注,请参考 [asdf actions repo](https://github.com/asdf-vm/actions) 了解现有的问题、对话和贡献指南。 ================================================ FILE: docs/zh-hans/guide/getting-started-legacy.md ================================================ # 快速入门 `asdf` 安装过程包括: 1. 安装依赖 2. 下载 `asdf` 核心 3. 安装 `asdf` 4. 为每一个你想要管理的工具/运行环境安装插件 5. 安装工具/运行环境的一个版本 6. 通过 `.tool-versions` 配置文件设置全局和项目版本 ## 1. 安装依赖 asdf 主要依赖 `git` 和 `curl`。这是一份 _非穷举_ 的命令清单用于运行 _你的_ 包管理器(有些可能在后面的步骤自动安装这些工具)。 | 操作系统| 包管理器 | 命令 | | ----- | --------------- | ---------------------------------- | | linux | Aptitude | `apt install curl git` | | linux | DNF | `dnf install curl git` | | linux | Pacman | `pacman -S curl git` | | linux | Zypper | `zypper install curl git` | | macOS | Homebrew | `brew install coreutils curl git` | | macOS | Spack | `spack install coreutils curl git` | ::: tip 注意 取决于你的系统配置,可能会需要 `sudo`。 ::: ## 2. 下载 asdf ### 官方支持的下载方式 <!-- x-release-please-start-version --> ```shell git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.15.0 ``` <!-- x-release-please-end --> ### 社区支持的下载方式 我们强烈推荐使用官方 `git` 方式。 | 方式 | 命令 | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Homebrew | `brew install asdf` | | Pacman | `git clone https://aur.archlinux.org/asdf-vm.git && cd asdf-vm && makepkg -si` 或者你希望使用 [AUR helper](https://wiki.archlinux.org/index.php/AUR_helpers) | ## 3. 安装 asdf 根据 Shell 脚本、操作系统和安装方法的组合不同,相应的配置也会不同。展开以下与你的系统最匹配的选项。 **macOS 用户,请务必阅读本节最后关于 `path_helper` 的警告。** ::: details Bash & Git 在 `~/.bashrc` 文件中加入以下内容: ```shell . "$HOME/.asdf/asdf.sh" ``` 补全功能必须在 `.bashrc` 文件中加入以下内容来配置完成: ```shell . "$HOME/.asdf/completions/asdf.bash" ``` ::: ::: details Bash & Git (macOS) 如果你正在使用 **macOS Catalina 或者更新的版本**, 默认的 shell 已经被修改为 **ZSH**。除非修改回 Bash, 否则请遵循 ZSH 的说明。 在 `~/.bash_profile` 文件中加入以下内容: ```shell . "$HOME/.asdf/asdf.sh" ``` 补全功能必须在 `.bash_profile` 文件中使用以下内容手动配置完成: ```shell . "$HOME/.asdf/completions/asdf.bash" ``` ::: ::: details Bash & Homebrew 使用以下命令将 `asdf.sh` 加入到 `~/.bashrc` 文件中: ```shell echo -e "\n. \"$(brew --prefix asdf)/libexec/asdf.sh\"" >> ~/.bashrc ``` 补全功能将需要 [按照 Homebrew 的说明完成配置](https://docs.brew.sh/Shell-Completion#configuring-completions-in-bash) 或者执行以下命令: ```shell echo -e "\n. \"$(brew --prefix asdf)/etc/bash_completion.d/asdf.bash\"" >> ~/.bashrc ``` ::: ::: details Bash & Homebrew (macOS) 如果你正在使用 **macOS Catalina 或者更新的版本**, 默认的 shell 已经被修改为 **ZSH**。除非修改回 Bash, 否则请遵循 ZSH 的说明。 使用以下命令将 `asdf.sh` 加入到 `~/.bash_profile` 文件中: ```shell echo -e "\n. \"$(brew --prefix asdf)/libexec/asdf.sh\"" >> ~/.bash_profile ``` 补全功能将需要 [按照 Homebrew 的说明完成配置](https://docs.brew.sh/Shell-Completion#configuring-completions-in-bash) 或者执行以下命令: ```shell echo -e "\n. \"$(brew --prefix asdf)/etc/bash_completion.d/asdf.bash\"" >> ~/.bash_profile ``` ::: ::: details Bash & Pacman 在 `~/.bashrc` 文件中加入以下内容: ```shell . /opt/asdf-vm/asdf.sh ``` 为了让补全功能正常工作需要安装 [`bash-completion`](https://wiki.archlinux.org/title/bash#Common_programs_and_options) 。 ::: ::: details Fish & Git 在 `~/.config/fish/config.fish` 文件中加入以下内容: ```shell source ~/.asdf/asdf.fish ``` 补全功能必须按照以下命令手动配置完成: ```shell mkdir -p ~/.config/fish/completions; and ln -s ~/.asdf/completions/asdf.fish ~/.config/fish/completions ``` ::: ::: details Fish & Homebrew 使用以下命令将 `asdf.fish` 加入到 `~/.config/fish/config.fish` 文件中: ```shell echo -e "\nsource "(brew --prefix asdf)"/libexec/asdf.fish" >> ~/.config/fish/config.fish ``` Fish shell 的补全功能可以交给 [Homebrew 处理](https://docs.brew.sh/Shell-Completion#configuring-completions-in-fish). 很友好! ::: ::: details Fish & Pacman 在 `~/.config/fish/config.fish` 文件中加入以下内容: ```shell source /opt/asdf-vm/asdf.fish ``` 补全功能将会在安装过程中由 AUR 包管理器自动配置完成。 ::: ::: details Elvish & Git 使用以下命令将 `asdf.elv` 加入到 `~/.config/elvish/rc.elv` 文件中: ```shell mkdir -p ~/.config/elvish/lib; ln -s ~/.asdf/asdf.elv ~/.config/elvish/lib/asdf.elv echo "\n"'use asdf _asdf; var asdf~ = $_asdf:asdf~' >> ~/.config/elvish/rc.elv echo "\n"'set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~' >> ~/.config/elvish/rc.elv ``` 补全功能将会自动配置。 ::: ::: details Elvish & Homebrew 使用以下命令将 `asdf.elv` 加入到 `~/.config/elvish/rc.elv` 文件中: ```shell mkdir -p ~/.config/elvish/lib; ln -s (brew --prefix asdf)/libexec/asdf.elv ~/.config/elvish/lib/asdf.elv echo "\n"'use asdf _asdf; var asdf~ = $_asdf:asdf~' >> ~/.config/elvish/rc.elv echo "\n"'set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~' >> ~/.config/elvish/rc.elv ``` 补全功能将会自动配置。 ::: ::: details Elvish & Pacman 使用以下命令将 `asdf.elv` 加入到 `~/.config/elvish/rc.elv` 文件中: ```shell mkdir -p ~/.config/elvish/lib; ln -s /opt/asdf-vm/asdf.elv ~/.config/elvish/lib/asdf.elv echo "\n"'use asdf _asdf; var asdf~ = $_asdf:asdf~' >> ~/.config/elvish/rc.elv echo "\n"'set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~' >> ~/.config/elvish/rc.elv ``` 补全功能将会自动配置。 ::: ::: details ZSH & Git 在 `~/.zshrc` 文件中加入以下内容: ```shell . "$HOME/.asdf/asdf.sh" ``` **或者** 使用 ZSH 框架插件,比如 [asdf for oh-my-zsh](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/asdf) 将会使脚本生效并安装补全功能。 补全功能会被 ZSH 框架 `asdf` 插件或者通过在 `.zshrc` 文件中加入以下内容自动配置: ```shell # append completions to fpath fpath=(${ASDF_DIR}/completions $fpath) # initialise completions with ZSH's compinit autoload -Uz compinit && compinit ``` - 如果你正在使用自定义的 `compinit` 配置,请确保 `compinit` 在 `asdf.sh` 生效位置的下方 - 如果你正在使用自定义的 `compinit` 配置和 ZSH 框架,请确保 `compinit` 在框架生效位置的下方 **警告** 如果你正在使用 ZSH 框架,有关的 `asdf` 插件或许需要更新才能通过 `fpath` 正确地使用最新的 ZSH 补全功能。Oh-My-ZSH asdf 插件还在更新中,请查看 [ohmyzsh/ohmyzsh#8837](https://github.com/ohmyzsh/ohmyzsh/pull/8837) 了解更多。 ::: ::: details ZSH & Homebrew 使用以下命令将 `asdf.sh` 加入到 `~/.zshrc` 文件中: ```shell echo -e "\n. $(brew --prefix asdf)/libexec/asdf.sh" >> ${ZDOTDIR:-~}/.zshrc ``` **或者** 使用 ZSH 框架插件,比如 [asdf for oh-my-zsh](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/asdf) 将会使脚本生效并安装补全功能。 补全功能可以被 ZSH 框架 `asdf` 或者 [按照 Homebrew 的指引](https://docs.brew.sh/Shell-Completion#configuring-completions-in-zsh) 完成配置。如果你正在使用 ZSH 框架,有关的 `asdf` 插件或许需要更新才能通过 `fpath` 正确地使用最新的 ZSH 补全功能。Oh-My-ZSH asdf 插件还在更新中,请查看 [ohmyzsh/ohmyzsh#8837](https://github.com/ohmyzsh/ohmyzsh/pull/8837) 了解更多。 ::: ::: details ZSH & Pacman 在 `~/.zshrc` 文件中加入以下内容: ```shell . /opt/asdf-vm/asdf.sh ``` 补全功能会被放在一个对 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 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 "\n$env.ASDF_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 "\n$env.ASDF_DIR = (brew --prefix asdf | str trim | into string | path join 'libexec')\n source " + (brew --prefix asdf | str trim | into string | path join 'libexec/asdf.nu') | save --append $nu.config-path ``` 补全功能将会自动配置。 ::: ::: details Nushell & Pacman 使用以下命令将 `asdf.nu` 加入到 `~/.config/nushell/config.nu` 文件中: ```shell "\n$env.ASDF_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 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 等等)**之后**的位置生效。 ::: warning 警告 在 macOS,启动 Bash 或 Zsh shell 会自动调用 `path_helper` 实用工具。`path_helper` 会重新排列 `PATH` (和 `MANPATH`) 中的条目,这可能会导致需要特定顺序的工具出现不一致的行为。为了解决这个问题,macOS 上的 `asdf` 默认会将其 `PATH`-条目强制添加到最前面(拥有最高优先级)。这可以通过 `ASDF_FORCE_PREPEND` 变量进行控制。 ::: 通常打开一个新的终端标签页来重启你的 shell 让 `PATH` 更改即时生效。 ## 核心安装完成! 这样就完成了 `asdf` 核心的安装 🎉 `asdf` 仅在你安装**插件**、**工具**和管理它们的**版本**时才开始真正发挥作用。请继续阅读下面的指南来了解这些是如何做到的。 ## 4. 安装插件 出于演示目的,我们将通过 [`asdf-nodejs`](https://github.com/asdf-vm/asdf-nodejs/) 插件来安装和设置 [Node.js](https://nodejs.org/)。 ### 插件依赖 每个插件都有依赖,所以我们需要确认应该列举了这些依赖的插件源码。对于 `asdf-nodejs` 来说,它们是: | 操作系统 | 安装依赖 | | ------------------------------ | --------------------------------------- | | Debian | `apt-get install dirmngr gpg curl gawk` | | CentOS/ Rocky Linux/ AlmaLinux | `yum install gnupg2 curl gawk` | | macOS | `brew install gpg gawk` | 我们应该提前安装这些依赖,因为有些插件有 post-install 钩子。 ### 安装插件 ```shell asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git ``` ## 5. 安装指定版本 现在我们已经有了 Node.js 插件,所以我们可以开始安装某个版本了。 我们通过 `asdf list all nodejs` 可以看到所有可用的版本或者通过 `asdf list all nodejs 14` 查看版本子集。 我们将只安装最新可用的 `latest` 版本: ```shell asdf install nodejs latest ``` ::: tip 注意 `asdf` 强制使用准确的版本。`latest` 是一个通过 `asdf` 来解析到执行时刻的实际版本号的辅助工具。 ::: ## 6. 设置默认版本 `asdf` 在从当前工作目录一直到 `$HOME` 目录的所有 `.tool-versions` 文件中进行工具的版本查找。查找在执行 `asdf` 管理的工具时实时发生。 ::: warning 警告 如果没有为工具找到指定的版本,则会出现**错误**。`asdf current` 将显示当前目录中的工具和版本解析结果,或者不存在,以便你可以观察哪些工具将无法执行。 ::: ### 全局 全局默认配置在 `$HOME/.tool-versions` 文件中进行管理。使用以下命令可以设置一个全局版本: ```shell asdf global nodejs latest ``` `$HOME/.tool-versions` 文件内容将会如下所示: ``` nodejs 16.5.0 ``` 某些操作系统已经有一些由系统而非 `asdf` 安装和管理的工具了,`python` 就是一个常见的例子。你需要告诉 `asdf` 将管理权还给系统。[版本参考部分](/zh-hans/manage/versions.md) 将会引导你。 ### 本地 本地版本被定义在 `$PWD/.tool-versions` 文件中(当前工作目录)。通常,这将会是一个项目的 Git 存储库。当在你想要的目录执行: ```shell asdf local nodejs latest ``` `$PWD/.tool-versions` 文件内容将会如下所示: ``` nodejs 16.5.0 ``` ### 使用现有工具版本文件 `asdf` 支持从其他版本管理器的现有版本文件中迁移过来,比如 `rbenv` 的 `.ruby-version` 文件。这在每个插件中都原生支持。 [`asdf-nodejs`](https://github.com/asdf-vm/asdf-nodejs/) 支持从 `.nvmrc` 和 `.node-version` 文件进行迁移。为了启用此功能,请在 `asdf` 配置文件 `$HOME/.asdfrc` 中加入以下内容: ``` legacy_version_file = yes ``` 请查看 [配置](/zh-hans/manage/configuration.md) 参考页面可以了解更多配置选项。 ## 完成指南! 恭喜你完成了 `asdf` 的快速上手 🎉 你现在可以管理你的项目的 `nodejs` 版本了。对于项目中的其他工具类型可以执行类似步骤即可! `asdf` 还有更多命令需要熟悉,你可以通过运行 `asdf --help` 或者 `asdf` 来查看它们。命令主要分为三类: - [`asdf` 核心](/zh-hans/manage/core.md) - [插件](/zh-hans/manage/plugins.md) - [(工具的)版本](/zh-hans/manage/versions.md) ================================================ FILE: docs/zh-hans/guide/getting-started.md ================================================ # 快速入门 ## 1. 安装 asdf asdf 的安装方式有以下几种: ::: details 使用包管理器 - **推荐** | 包管理器 | 命令 | | -------- | ----- | | Homebrew | `brew install asdf` | | Zypper | `zypper install asdf` | | Pacman | `git clone https://aur.archlinux.org/asdf-vm.git && cd asdf-vm && makepkg -si` 或者你希望使用 [AUR helper](https://wiki.archlinux.org/index.php/AUR_helpers) | ::: :::: details 下载预编译二进制 - **简单** <!--@include: @/zh-hans/parts/install-dependencies.md--> ##### 安装 asdf 1. 访问 https://github.com/asdf-vm/asdf/releases 并下载与操作系统和架构匹配的压缩包。 2. 从压缩包中解压 `asdf` 二进制文件到 `$PATH` 路径的某个文件夹. 3. 运行 `type -a asdf` 来验证 `asdf` 是否已经在 `$PATH` 路径中。放置 `asdf` 二进制文件的目录应该包含在 `type` 命令的输出中。如果不在,那么意味着第 2 步不是完全正确。 :::: :::: details 使用 `go install` <!--@include: @/zh-hans/parts/install-dependencies.md--> ##### 安装 asdf <!-- x-release-please-start-version --> 1. [安装 Go](https://go.dev/doc/install) 2. 运行 `go install github.com/asdf-vm/asdf/cmd/asdf@v0.18.1` <!-- x-release-please-end --> :::: :::: details 从源码构建 <!--@include: @/zh-hans/parts/install-dependencies.md--> ##### 安装 asdf <!-- x-release-please-start-version --> 1. 克隆 asdf 仓库: ```shell git clone https://github.com/asdf-vm/asdf.git --branch v0.18.1 ``` <!-- x-release-please-end --> 2. 运行 `make` 3. 复制 `asdf` 二进制文件到 `$PATH` 路径的某个文件夹. 4. 运行 `type -a asdf` 来验证 `asdf` 是否已经在 `$PATH` 路径中。放置 `asdf` 二进制文件的目录应该包含在 `type` 命令的输出中。如果不在,那么意味着第 3 步不是完全正确。 :::: ## 2. 配置 asdf ::: tip 注意 大部分用户 **不** 需要自定义 asdf 插件、安装包、垫片数据的位置。但是,如果 `$HOME/.asdf` 不是你想要 asdf 写入的目录,你可以修改它。请通过在 Shell 的 RC 文件中定义 `ASDF_DATA_DIR` 变量来指定你想要的目录。 ::: 根据 Shell 脚本、操作系统和安装方法的组合不同,相应的配置方式也会有所不同。展开以下与你的系统最匹配的选项。 **macOS 用户,请务必阅读本节最后关于 `path_helper` 的警告。** ::: details Bash **macOS Catalina 或者更新的版本**: 默认的 shell 已经被修改为 **ZSH**。除非修改回 Bash, 否则请遵循 ZSH 的说明。 **Pacman**: 补全功能需要安装 [`bash-completion`](https://wiki.archlinux.org/title/bash#Common_programs_and_options)。 ##### 将垫片目录添加到路径(必须) 在 `~/.bash_profile` 文件中添加以下内容: ```shell export PATH="${ASDF_DATA_DIR:-$HOME/.asdf}/shims:$PATH" ``` ###### 自定义数据目录(可选) 在 `~/.bash_profile` 文件中上面一行声明之前添加以下变量声明: ```shell export ASDF_DATA_DIR="/your/custom/data/dir" ``` ##### 设置 shell 补全(可选) 在 `.bashrc` 文件中添加下面内容来配置补全功能: ```shell . <(asdf completion bash) ``` ::: ::: details Fish ##### 将垫片目录添加到路径(必须) 在 `~/.config/fish/config.fish` 文件中添加以下内容: ```shell # ASDF configuration code if test -z $ASDF_DATA_DIR set _asdf_shims "$HOME/.asdf/shims" else 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 PATH if not contains $_asdf_shims $PATH set -gx --prepend PATH $_asdf_shims end set --erase _asdf_shims ``` ###### 自定义数据目录(可选) **Pacman**: 补全功能会在 AUR 包安装时自动配置。 在 `~/.config/fish/config.fish` 文件中上面一行声明之前添加下面内容: ```shell set -gx --prepend ASDF_DATA_DIR "/your/custom/data/dir" ``` ##### 设置 shell 补全(可选) 必须通过以下命令手动配置补全功能: ```shell $ asdf completion fish > ~/.config/fish/completions/asdf.fish ``` ::: ::: details Elvish ##### 将垫片目录添加到路径(必须) 在 `~/.config/elvish/rc.elv` 文件中添加以下内容: ```shell var asdf_data_dir = ~'/.asdf' if (and (has-env ASDF_DATA_DIR) (!=s $E:ASDF_DATA_DIR '')) { set asdf_data_dir = $E:ASDF_DATA_DIR } if (not (has-value $paths $asdf_data_dir'/shims')) { set paths = [$path $@paths] } ``` ###### 自定义数据目录(可选) 修改在上面片段之前的如下一行内容为自定义数据目录: ```diff -var asdf_data_dir = ~'/.asdf' +var asdf_data_dir = '/your/custom/data/dir' ``` ##### 设置 shell 补全(可选) ```shell $ asdf completion elvish >> ~/.config/elvish/rc.elv $ echo "\n"'set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~' >> ~/.config/elvish/rc.elv ``` ::: ::: details ZSH **Pacman**: 补全功能被放置在对 ZSH 友好的位置,但是 [ZSH 必须配置使用自动补全](https://wiki.archlinux.org/index.php/zsh#Command_completion)。 ##### 将垫片目录添加到路径(必须) 在 `~/.zshrc` 文件中添加以下内容: ```shell export PATH="${ASDF_DATA_DIR:-$HOME/.asdf}/shims:$PATH" ``` ###### 自定义数据目录(可选) 在 `~/.zshrc` 文件中上面一行声明之前添加以下内容: ```shell export ASDF_DATA_DIR="/your/custom/data/dir" ``` ##### 设置 shell 补全(可选) 补全功能可以通过 ZSH 框架的 `asdf` 插件 (类似 [asdf for oh-my-zsh](https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/asdf)) 或如下操作启用: ```shell $ mkdir -p "${ASDF_DATA_DIR:-$HOME/.asdf}/completions" $ asdf completion zsh > "${ASDF_DATA_DIR:-$HOME/.asdf}/completions/_asdf" ``` 然后在 `.zshrc` 文件中添加以下内容: ```shell # 添加补全功能到 fpath fpath=(${ASDF_DATA_DIR:-$HOME/.asdf}/completions $fpath) # 使用 ZSH 的 compinit 初始化补全功能 autoload -Uz compinit && compinit ``` **注意** 如果你正在 ZSH 框架中使用自定义的 `compinit` 设置 ,请确保 `compinit` 在框架加载之后加载。 补全功能可以通过 ZSH 框架 `asdf` 或者将需要 [按照 Homebrew 的说明进行配置](https://docs.brew.sh/Shell-Completion#configuring-completions-in-zsh). 如果你正在使用 ZSH 框架,与 asdf 关联的插件或许需要更新以便通过 `fpath` 正确使用新 ZSH。 Oh-My-ZSH asdf 插件尚未更新,请查看 [ohmyzsh/ohmyzsh#8837](https://github.com/ohmyzsh/ohmyzsh/pull/8837) 了解更多。 ::: ::: details PowerShell Core ##### 将垫片目录添加到路径(必须) 在 `~/.config/powershell/profile.ps1` 文件中添加以下内容: ```shell # 确定垫片目录的位置 if ($null -eq $ASDF_DATA_DIR -or $ASDF_DATA_DIR -eq '') { $_asdf_shims = "${env:HOME}/.asdf/shims" } else { $_asdf_shims = "$ASDF_DATA_DIR/shims" } # 然后添加到 path 路径 $env:PATH = "${_asdf_shims}:${env:PATH}" ``` ###### 自定义数据目录(可选) 在 `~/.config/powershell/profile.ps1` 文件中上面片段之前添加以下内容: ```shell $env:ASDF_DATA_DIR = "/your/custom/data/dir" ``` Shell 补全功能不支持 PowerShell。 ::: ::: details Nushell ##### 将垫片目录添加到路径(必须) 在 `~/.config/nushell/config.nu` 文件中添加以下内容: ```shell let shims_dir = ( if ( $env | get --ignore-errors ASDF_DATA_DIR | is-empty ) { $env.HOME | path join '.asdf' } else { $env.ASDF_DATA_DIR } | path join 'shims' ) $env.PATH = ( $env.PATH | split row (char esep) | where { |p| $p != $shims_dir } | prepend $shims_dir ) ``` ###### 自定义数据目录(可选) 在 `~/.config/nushell/config.nu` 文件中上面内容之前添加下面变量声明: ```shell $env.ASDF_DATA_DIR = "/your/custom/data/dir" ``` ##### 设置 shell 补全(可选) ```shell # If you've not customized the asdf data directory: $ mkdir $"($env.HOME)/.asdf/completions" $ asdf completion nushell | save $"($env.HOME)/.asdf/completions/nushell.nu" # If you have customized the data directory by setting ASDF_DATA_DIR: $ mkdir $"($env.ASDF_DATA_DIR)/completions" $ asdf completion nushell | save $"($env.ASDF_DATA_DIR)/completions/nushell.nu" ``` 然后在 `~/.config/nushell/config.nu` 文件中添加以下内容: ```shell let asdf_data_dir = ( if ( $env | get --ignore-errors ASDF_DATA_DIR | is-empty ) { $env.HOME | path join '.asdf' } else { $env.ASDF_DATA_DIR } ) . "$asdf_data_dir/completions/nushell.nu" ``` ::: ::: details POSIX Shell ##### 将垫片目录添加到路径(必须) 在 `~/.profile` 文件中添加以下内容: ```shell export PATH="${ASDF_DATA_DIR:-$HOME/.asdf}/shims:$PATH" ``` ###### 自定义数据目录(可选) 在 `~/.profile` 文件中上面一行内容之前添加以下内容: ```shell export ASDF_DATA_DIR="/your/custom/data/dir" ``` ::: `asdf` 脚本需要在设置好的 `$PATH` **之后**和已经生效的框架(比如 oh-my-zsh 等等)**之后**的位置生效。 通常打开一个新的终端标签页来重启你的 shell 让 `PATH` 更改即时生效。 ## 核心安装完成! 这样就完成了 `asdf` 核心的安装 🎉 `asdf` 仅在你安装**插件**、**工具**和管理它们的**版本**时才开始真正发挥作用。请继续阅读下面的指南来了解这些是如何做到的。 ## 4. 安装插件 出于演示目的,我们将通过 [`asdf-nodejs`](https://github.com/asdf-vm/asdf-nodejs/) 插件来安装和设置 [Node.js](https://nodejs.org/)。 ### 插件依赖 每个插件都有依赖,所以我们需要确认应该列举了这些依赖的插件源码。对于 `asdf-nodejs` 来说,它们是: | 操作系统 | 安装依赖 | | ------------------------------ | --------------------------------------- | | Debian | `apt-get install dirmngr gpg curl gawk` | | CentOS/ Rocky Linux/ AlmaLinux | `yum install gnupg2 curl gawk` | | macOS | `brew install gpg gawk` | 我们应该提前安装这些依赖,因为有些插件有 post-install 钩子。 ### 安装插件 ```shell asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs.git ``` ## 5. 安装指定版本 现在我们已经有了 Node.js 插件,所以我们可以开始安装某个版本了。 我们通过 `asdf list all nodejs` 可以看到所有可用的版本或者通过 `asdf list all nodejs 14` 查看版本子集。 我们将只安装最新可用的 `latest` 版本: ```shell asdf install nodejs latest ``` ::: tip 注意 `asdf` 强制使用准确的版本。`latest` 是一个通过 `asdf` 来解析到执行时刻的实际版本号的辅助工具。 ::: ## 6. 设置默认版本 `asdf` 在从当前工作目录一直到 `$HOME` 目录的所有 `.tool-versions` 文件中进行工具的版本查找。查找在执行 `asdf` 管理的工具时实时发生。 ::: warning 警告 如果没有为工具找到指定的版本,则会出现**错误**。`asdf current` 将显示当前目录中的工具和版本解析结果,或者不存在,以便你可以观察哪些工具将无法执行。 ::: 因为 asdf 会在当前目录寻找 `.tool-versions` 文件,如果没有找到将会继续逐层向上在父目录寻找 `.tool-versions` 文件直到找到。如果在父目录也没有找到 `.tool-versions` 文件,版本解析进程将会失败并且打印错误。 如果你想要设置一个默认版本用来应用在你工作的所有目录,你可以在 `$HOME/.tool-versions` 文件中定义版本。任何在家目录下的子目录都会被解析为同样的版本,除非子目录中设置了另外一个版本。 ```shell asdf set -u nodejs 16.5.0 ``` `$HOME/.tool-versions` 文件内容将会变成: ``` nodejs 16.5.0 ``` 某些操作系统已经有一些由系统而非 `asdf` 安装和管理的工具了,`python` 就是一个常见的例子。你需要告诉 `asdf` 将管理权还给系统。[版本](/zh-hans/manage/versions.md) 参考页面将会引导你。 asdf 首先从当前工作目录的 `$PWD/.tool-versions` 文件中寻找版本。这可能是一个包含源代码或某个项目 Git 存储库的目录。当在你想要的目录执行时,你可以用 `asdf set` 来设置版本: ```shell asdf set nodejs 16.5.0 ``` `$PWD/.tool-versions` 文件内容将会变成: ``` nodejs 16.5.0 ``` ### 使用现有工具版本文件 `asdf` 支持从其他版本管理器的现有版本文件中迁移过来,比如 `rbenv` 的 `.ruby-version` 文件。这在每个插件中都原生支持。 [`asdf-nodejs`](https://github.com/asdf-vm/asdf-nodejs/) 支持从 `.nvmrc` 和 `.node-version` 文件进行迁移。为了启用此功能,请在 `asdf` 配置文件 `$HOME/.asdfrc` 中添加以下内容: ``` legacy_version_file = yes ``` 查看 [配置](/zh-hans/manage/configuration.md) 参考页面可以了解更多配置选项。 ## 完成指南! 恭喜你完成了 `asdf` 的快速上手 🎉 你现在可以管理你的项目的 `nodejs` 版本了。对于项目中的其他工具类型可以执行类似步骤即可! `asdf` 还有更多命令需要熟悉,你可以通过运行 `asdf --help` 或者 `asdf` 来查看它们。命令主要分为三类: - [`asdf` 核心](/zh-hans/manage/core.md) - [插件](/zh-hans/manage/plugins.md) - [(工具的)版本](/zh-hans/manage/versions.md) ================================================ FILE: docs/zh-hans/guide/introduction.md ================================================ # 项目简介 `asdf` 是一个工具版本管理器。所有的工具版本定义都包含在一个文件(`.tool-versions`)中,你可以将配置文件放在项目的 Git 存储库中以便于和团队其他成员共享,从而确保每个人都使用**完全**相同的工具版本。 传统工作方式需要多个命令行版本管理器,而且每个管理器都有其不同的 API、配置文件和实现方式(比如,`$PATH` 操作、垫片、环境变量等等)。`asdf` 提供单个交互方式和配置文件来简化开发工作流程,并可通过简单的插件接口扩展到所有工具和运行环境。 ## 工作原理 一旦 `asdf` 核心在 Shell 配置中设置好之后,你可以安装插件来管理特定的工具。当通过插件安装工具时,安装的可执行程序会为每个可执行程序创建 [垫片](<https://zh.wikipedia.org/wiki/%E5%9E%AB%E7%89%87_(%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1)>)。当你尝试运行其中一个可执行程序时,将运行垫片,从而让 `asdf` 识别 `.tool-versions` 文件中的工具版本并执行该版本。 ## 相关项目 ### nvm / n / rbenv 等 [nvm](https://github.com/nvm-sh/nvm), [n](https://github.com/tj/n) 和 [rbenv](https://github.com/rbenv/rbenv) 等工具都是用 Shell 脚本写的,这些脚本能为工具安装的可执行程序创建垫片。 `asdf` 非常相似,目的是在工具/运行环境版本管理领域竞争。`asdf` 的独特之处在于插件系统,它消除了每个工具/运行环境对管理工具的需求、每个管理工具的不同命令以及存储库中不同的`*-版本`文件。 <!-- ### pyenv TODO: someone with Python background expand on this `asdf` has some similarities to `pyenv` but is missing some key features. The `asdf` team is looking at introducing some of these `pyenv` specific features, though no roadmap or timeline is available. --> ### direnv > 以“根据当前所在目录动态加载和卸载环境变量”的新特性来增强现有 shell。 `asdf` 不直接管理环境变量,但是插件 [`asdf-direnv`](https://github.com/asdf-community/asdf-direnv) 可以集成 direnv 的特性到 `asdf` 中。 请查看 [direnv 文档](https://direnv.net/) 了解更多。 ### Homebrew > macOS(或者 Linux)上缺失包的管理器 Homebrew 管理你的软件包及其上游依赖。`asdf` 不管理上游依赖,它不是包管理器(这应该由用户来负责),尽管我们试图保持依赖列表尽可能小。 请查看 [Homebrew 文档](https://brew.sh/) 了解更多。 ### NixOS > Nix 是一种采用独特方法进行软件包管理和系统配置的工具 NixOS 旨在通过管理每个工具的整个依赖关系树中软件包的确切版本来构建真正可重复的环境,这不是 `asdf` 想做的。NixOS 使用自己的编程语言、许多命令行工具和超过 60,000 个包的包集合来实现这一点。 再次声明,`asdf` 不管理上游依赖,并且它不是一个包管理器。 请查看 [NixOS 文档](https://nixos.org/guides/how-nix-works.html) 了解更多。 ## 为什么使用 asdf? `asdf` 确保团队可以使用**完全**相同的工具版本,通过插件系统支持**很多**工具,以及作为 Shell 配置中包含的单个 **Shell** 脚本的 _简单性和熟悉性_ 。 ::: tip 注意 `asdf` 并不打算成为一个系统包管理器。它是一个工具版本管理器。仅仅因为你可以为任何工具创建插件并使用 `asdf` 管理其版本,但这并不意味着一定是某个特定工具的最佳实践方案。 ::: ================================================ FILE: docs/zh-hans/guide/upgrading-to-v0-16.md ================================================ # 升级到 0.16.0 asdf 0.15.0 版本以及更早版本是用 Bash 编写的,并以一系列 Bash 脚本的形式分发,其中 `asdf` 函数加载到 shell 中。从 0.16.0 版本开始 asdf 用 Go 语言完全重写了。由于是完全重写,存在一些 [重大变更](#重大变更),现在它是一个二进制文件,而不是一系列脚本。 ## 安装 0.16.0 版本及更高版本的安装比之前的 asdf 版本要简单多了。只需要三个步骤: * 通过 [任意可能的安装方式](/zh-hans/guide/getting-started.html#_1-安装-asdf) 下载与操作系统和架构匹配的 `asdf` 二进制文件。如果使用包管理器,请验证安装的是 0.16.0 版本及更高版本。 * 添加 `$ASDF_DATA_DIR/shims` 变量到 `$PATH` 路径的最前面。 * 可选的是,如果你之前自定义了 asdf 数据目录,请将 `ASDF_DATA_DIR` 变量设置为包含插件、版本和垫片的旧版本安装目录。 如果操作系统包管理器已经提供 asdf 0.16.0,那么使用它来安装 asdf 可能是最佳方法。现在,升级 asdf 只能通过操作系统包管理器或手动安装来完成,不再支持自动升级功能。 ### 不丢失数据的升级 你可以升级到 asdf 的最新版本,而不会丢失现有的安装数据。操作步骤同上。 #### 1. 下载匹配操作系统和架构的 `asdf` 二进制文件 从 [GitHub 发布页面](https://github.com/asdf-vm/asdf/releases) 下载二进制文件,并将其放置在系统路径中的某个目录下。我选择将 asdf 二进制文件放置在 `$HOME/bin` 目录中,然后将 `$HOME/bin` 添加在 `$PATH` 路径最前面: ``` # 在 .zshrc, .bashrc, 等... export PATH="$HOME/bin:$PATH" ``` #### 2. 设置 `ASDF_DATA_DIR` 运行 `asdf info` 并复制包含 `ASDF_DATA_DIR` 变量的行: ``` ... ASDF_DATA_DIR="/home/myuser/.asdf" ... ``` 在 shell RC 配置文件中(比如 Zsh 的 `.zshrc`,Bash 的 `.bashrc` 等)末尾添加一行设置 `ASDF_DATA_DIR` 为相同的值: ```bash export ASDF_DATA_DIR="/home/myuser/.asdf" ``` #### 3. 将 `$ASDF_DATA_DIR/shims` 加在 `$PATH` 最前面 在 shell RC 配置文件(与第 2 步相同的文件)中,将 `$ASDF_DATA_DIR/shims` 添加到路劲的开头: ```bash export ASDF_DATA_DIR="/home/myuser/.asdf" export PATH="$ASDF_DATA_DIR/shims:$PATH" ``` #### 4. 移除旧的配置文件 在 shell RC 配置中,你会有旧代码在启动时运行 asdf shell 脚本。它可能看起来像这样: ``` . "$HOME/.asdf/asdf.sh" ``` 或者这样: ``` . /opt/homebrew/opt/asdf/libexec/asdf.sh ``` 注释掉这些行或者完全删除它们。 如果你未使用 Zsh 或者 Bash,请查阅旧版的 [快速入门](https://asdf-vm.com/zh-hans/guide/getting-started-legacy.html#_3-安装-asdf) 获悉需要删除的代码片段。 #### 5. 重新生成垫片 请通过运行 `asdf --help` 命令,确认当前 shell 会话中 `asdf` 命令的版本为 0.16.0 或更高版本。如果仍显示旧版本,你需要启动一个新的 shell 会话。 一旦确认 `asdf` 命令为新版本后,运行 `asdf reshim` 来重新生成所有的垫片。这是必要的,因为旧的垫片可能仍使用旧的 Bash 版本。 ### 测试 如果你不确定升级到 0.16.0 是否会导致问题,那么可以按照上述“不丢失数据的升级”的描述,在现有版本的基础上安装 0.16.0 进行测试。如果升级到 0.16.0 或更高版本导致问题,你可以回退到旧版本。删除添加在 shell RC 配置文件中的行,并重新添加删除或注释掉的行即可。 ### 移除旧文件 **仅在完成上述所有步骤并确认新的 asdf 安装正常运行后再执行此操作!** 升级后,你可以从旧版基于 Bash 脚本的 asdf 版本中移除旧文件。数据目录(通常为 `~/.asdf/`)中的大多数文件均可删除。需要注意的是,此操作并非强制要求。保留旧版本 asdf 的文件不会造成任何问题。必须**保留**的目录仅有: * `downloads/` * `installs/` * `plugins/` * `shims/` 其余文件可以删除。这可以通过 `find` 命令一次性完成: ``` find ${ASDF_DATA_DIR:-$HOME/.asdf}/ -maxdepth 1 -mindepth 1 -not -name downloads -not -name plugins -not -name installs -not -name shims -exec rm -r {} \; ``` ## 重大变更 ### 连字符连接的命令已被移除 asdf 版本 0.15.0 及更早版本对某些命令支持带连字符和不带连字符。从版本 0.16.0 开始,仅支持不带连字符的版本。受影响的命令有: * `asdf list-all` -> `asdf list all` * `asdf plugin-add` -> `asdf plugin add` * `asdf plugin-list` -> `asdf plugin list` * `asdf plugin-list-all` -> `asdf plugin list all` * `asdf plugin-update` -> `asdf plugin update` * `asdf plugin-remove` -> `asdf plugin remove` * `asdf plugin-test` -> `asdf plugin test` * `asdf shim-versions` -> `asdf shimversions` ### `asdf global` 和 `asdf local` 命令已被 `asdf set` 取代 `asdf global` 和 `asdf local` 已被移除。"global" 和 "local" 这一术语存在错误且容易引起误解。asdf 实际上并不支持适用于所有位置的 "global" 版本。任何通过 `asdf global` 指定的版本都可能被当前目录中的 `.tool-versions` 文件中指定的不同版本覆盖。这会让用户感到困惑。新的 `asdf set` 默认行为与 `asdf local` 相同,但还提供了用于在用户主目录(`--home`)和父目录中的现有 `.tool-versions` 文件(`--parent`)中设置版本的标志。这个新接口有望更好地传达 asdf 如何解析版本,并提供等效的功能。 ### `asdf update` 命令已被移除 更新不再支持此方式。请使用操作系统包管理器或手动下载最新二进制文件。此外,版本 0.15.0 及更早版本中存在的 `asdf update` 命令无法升级到 0.16.0 版本,因为安装流程已经发生了改变。**无法通过 `asdf update` 命令升级到最新 Go 实现版本。** ### `asdf shell` 命令已被移除 该命令实际上在用户的当前 shell 会话中设置了一个环境变量。它能够做到这一点是因为 `asdf` 实际上是一个 shell 函数,而不是可执行文件。新的重写版本移除了 asdf 中的所有 shell 代码,现在它是一个二进制文件而非 shell 函数,因此直接在 shell 中设置环境变量已不再可能。 ### `asdf current` 已发生改变 输出中不再显示三列,最后一列不再显示版本设置的位置或建议用于设置或安装的命令。第三列已拆分成两列。现在的第三列仅指示版本的来源(如果已设置,通常为版本文件或环境变量),而第四列是布尔值,表示指定的版本是否实际已安装。如果未安装,将显示建议的安装命令。 ### 插件扩展命令现在必须以 `cmd` 为前缀 之前的插件扩展命令可以像这样运行: ``` asdf nodejs nodebuild --version ``` 现在它们必须以 `cmd` 为前缀,以避免与内置命令混淆: ``` asdf cmd nodejs nodebuild --version ``` ### 扩展命令已重新设计 插件扩展命令有一系列的重大变更: * 它们必须可以通过 `exec` 系统调用运行。若扩展命令是 shell 脚本,为了能通过 `exec` 运行,它们必须以正确的 shebang( `#!`)行开头。 * 它们现在可以是任何语言的二进制文件或脚本。不再要求使用 `.bash` 扩展名,因为这会引起误解。 * 它们必须具有可执行权限。 * 当缺少可执行权限时,它们不再被 asdf 作为 Bash 脚本加载。 此外,仅使用插件名称后的第一个参数来确定要运行的扩展命令。这意味着实际上存在一个默认的 `command` 扩展命令,当未找到与插件名称后第一个参数匹配的命令时,asdf 会默认使用该命令。例如: ``` foo/ lib/commands/ command command-bar command-bat-man ``` 先前这些脚本的工作方式是这样的: ``` $ asdf cmd foo # 等同于运行 `$ASDF_DATA_DIR/plugins/foo/lib/commands/command` $ asdf cmd foo bar # 等同于运行 `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-bar` $ asdf cmd foo bat man # 等同于运行 `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-bat-man` ``` 现在: ``` $ asdf cmd foo # 等同于运行 `$ASDF_DATA_DIR/plugins/foo/lib/commands/command` $ asdf cmd foo bar # 等同于运行 `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-bar` $ asdf cmd foo bat man # 等同于运行 `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-bat man` ``` ### 可执行文件的兼容性问题由 `syscall.Exec` 解决 最明显的例子是缺少正确 shebang 行的脚本。asdf 0.15.0 及更早版本使用 Bash 实现,因此只要该可执行文件可通过 Bash 执行,即可运行。这意味着缺少 shebang 行的脚本仍可通过 `asdf exec` 运行。随着 asdf 0.16.x 改用 Go 语言实现,我们现在通过 Go 的 `syscall.Exec` 函数调用可执行文件,而该函数无法处理缺少 shebang 行的脚本。 实际上这并不是什么大问题。大多数 shell 脚本确实包含 shebang 行。如果由 asdf 管理且缺少 shebang 行,则需要手动添加。 ### 不再支持自定义垫片模版 这是一个鲜少使用的功能。核心团队维护的唯一使用该功能的插件是 Elixir 插件,而该插件现已不再需要此功能。该功能最初添加的目的是,使由程序评估而非执行的垫片包含适合特定程序评估的代码(在 Elixir 的情况下,这是 `iex` shell。)经过进一步调查,似乎该功能仅存在于 `PATH` 环境变量中可执行文件路径有时被错误地设置为包含**垫片**文件而非其他**可执行文件**的情况,且该设置针对选定的版本。 ================================================ FILE: docs/zh-hans/index.md ================================================ --- # https://vitepress.dev/reference/default-theme-home-page layout: home hero: name: asdf text: 多运行时版本管理器 tagline: 使用一个工具管理所有运行时版本! actions: - theme: brand text: 快速上手 link: /zh-hans/guide/getting-started - theme: alt text: 什么是asdf? link: /zh-hans/guide/introduction - theme: alt text: 在 Github 上查看 link: https://github.com/asdf-vm/asdf features: - title: 一个工具 details: "使用单个命令行工具和命令界面管理你的每个项目运行环境。" icon: 🎉 - title: 插件 details: "现有运行环境和工具的大型生态系统。简单 API 用于根据需要添加对新工具的支持!" icon: 🔌 - title: 向后兼容 details: "支持从现有配置文件 .nvmrc、.node-version、.ruby-version 平滑迁移!" icon: ⏮ - title: "一个配置文件" details: "一个可共享的 .tool-versions 配置文件管理所有工具、运行环境及其版本。" icon: 📄 - title: "Shells" details: "支持 Bash、ZSH、Fish 和 Elvish,并提供补全功能。" icon: 🐚 - title: "GitHub Actions" details: "提供 Github Action 在 CI/CD 工作流中安装和使用 .tool-versions。" icon: 🤖 --- ================================================ FILE: docs/zh-hans/manage/commands.md ================================================ # 所有命令 `asdf` 中所有可用命令的列表。这个列表就是 `asdf help` 命令的打印内容。 <<< @../../internal/help/help.txt ================================================ FILE: docs/zh-hans/manage/configuration.md ================================================ # 配置 `asdf` 配置既包括可共享的 `.tool-versions` 文件,也包括用户特定的自定义 `.asdfrc` 和环境变量。 ## `.tool-versions` 无论何时 `.tool-versions` 出现在目录中,它所声明的工具版本将会被用于该目录和任意子目录。 `.tool-versions` 文件示例如下所示: ``` ruby 2.5.3 nodejs 10.15.0 ``` 你也可以包含注释在里面: ``` ruby 2.5.3 # 这是一个注释 # 这是另一个注释 nodejs 10.15.0 ``` 版本号可以有如下格式: - `10.15.0` - 实际的版本号。支持下载二进制文件的插件将会下载二进制文件。 - `ref:v1.0.2-a` 或者 `ref:39cb398vb39` - 指定标签/提交/分支从 github 下载并编译。 - `path:~/src/elixir` - 要使用的工具的自定义编译版本的路径。这种方式供语言开发者等使用。 - `system` - 此关键字会导致 asdf 传递系统上未由 asdf 管理的工具版本。 ::: tip 提示 多版本可以通过空格将它们分隔开来。比如,使用 Python `3.7.2` 回退到 Python `2.7.15` 最后回退到 `system` Python,可以将以下行的内容添加到 `.tool-versions` 文件中。 ``` python 3.7.2 2.7.15 system ``` ::: 为了安装 `.tool-versions` 文件中定义的所有工具,在包含 `.tool-versions` 文件的目录中不带其他参数执行 `asdf install` 命令。 为了安装 `.tool-versions` 文件中定义的某个工具,在包含 `.tool-versions` 文件的目录中运行 `asdf install <name>` 命令。这个工具将会安装 `.tool-versions` 文件所指定的版本。 可以直接编辑这个文件或者使用 `asdf local` (或者 `asdf global`)来更新工具版本。 ## `.asdfrc` `.asdfrc` 文件定义了用户机器的特定配置。 `$HOME/.asdfrc` 是 asdf 使用的默认位置。这可以通过 [环境变量 `ASDF_CONFIG_FILE`](#asdf-config-file) 进行配置。 以下文件展示了所需的格式及其默认值: ```txt legacy_version_file = no use_release_candidates = no always_keep_download = no plugin_repository_last_check_duration = 60 disable_plugin_short_name_repository = no concurrency = auto ``` ### `legacy_version_file` 插件 **支持** 读取其他版本管理器使用的版本文件,比如,Ruby 的 `rbenv` 的 `.ruby-version` 文件。 | 选项 | 描述 | | :------------------------------------------------------ | :------------------------------------------------------- | | `no` <Badge type="tip" text="默认" vertical="middle" /> | 从 `.tool-versions` 文件读取版本 | | `yes` | 如果可行的话,从传统版本文件读取版本(`.ruby-versions`) | ### `always_keep_download` 配置 `asdf install` 命令以保留或删除下载的源代码或二进制文件。 | 选项 | 描述 | | :------------------------------------------------------ | :--------------------------------- | | `no` <Badge type="tip" text="默认" vertical="middle" /> | 在成功安装后删除源代码或二进制文件 | | `yes` | 在安装后保留源代码或二进制文件 | ### `plugin_repository_last_check_duration` 配置自上次 asdf 插件存储库同步到下一次存储库同步的持续时间。命令 `asdf plugin add <name>` 或者 `asdf plugin list all` 将会触发持续时间的检查,如果持续时间已过,则进行同步。 | 选项 | 描述 | | :-------------------------------------------------------------------------------------------- | :------------------------------------------------- | | 从 `1` 到 `999999999` 的数字 <br/> <Badge type="tip" text="默认" vertical="middle" /> 为 `60` | 如果已过自上次同步的持续时间,触发器事件发生时同步 | | `0` | 每个触发器事件发生时同步 | | `never` | 从不同步 | 同步事件在执行以下命令时发生: - `asdf plugin add <name>` - `asdf plugin list all` `asdf plugin add <name> <git-url>` 不会触发插件同步。 ::: warning 注意 将值设置为 `never` 并不会阻止插件仓库的初始同步,如需实现此行为,请查看 `disable_plugin_short_name_repository` 了解更多。 ::: ### `disable_plugin_short_name_repository` 禁用 asdf 插件的缩写仓库同步功能。如果缩写仓库被禁用,同步事件将提前退出。 | 选项 | 描述 | | :--------------------------------------------------------- | :--------------------------------- | | `no` <Badge type="tip" text="default" vertical="middle" /> | 在同步事件发生时克隆或更新 asdf 插件仓库 | | `yes` | 禁用插件缩写仓库 | 同步事件在执行以下命令时发生: - `asdf plugin add <name>` - `asdf plugin list all` `asdf plugin add <name> <git-url>` 不会触发插件同步。 ::: warning 注意 禁用插件缩写仓库不会删除该仓库,如果它已经同步过。使用 `rm --recursive --trash $ASDF_DATA_DIR/repository` 才可以删除插件仓库。 禁用插件缩写仓库不会删除从该源之前安装的插件。可使用 `asdf plugin remove <name>` 命令删除插件。删除插件将移除该管理工具所有已安装版本。 ::: ### `concurrency` 编译时使用的默认核心数。 | 选项 | 描述 | | :------ | :--------------------------------------------------------------------------------------------------- | | integer | 编译源代码时使用的核心数 code | | `auto` | 使用 `nproc` 命令计算核心数量,然后使用 `sysctl hw.ncpu` 命令,接着查看 `/proc/cpuinfo` 文件,如果无法获取则默认使用 `1`。 | 注意:如果设置了环境变量 `ASDF_CONCURRENCY`,则该变量具有优先级。 ### 插件钩子 可以执行自定义代码: - 在插件安装、重新加载、更新或卸载之前或之后 - 在执行插件命令之前或之后 比如,如果安装了一个名为 `foo` 的插件并提供了 `bar` 可执行文件,则可以使用以下钩子在执行插件命令之前先执行自定义代码: ```text pre_foo_bar = echo Executing with args: $@ ``` 支持以下模式: - `pre_<plugin_name>_<command>` - `pre_asdf_download_<plugin_name>` - `{pre,post}_asdf_{install,reshim,uninstall}_<plugin_name>` - `$1`: 完整版本 - `{pre,post}_asdf_plugin_{add,update,remove,reshim}` - `$1`: 插件名称 - `{pre,post}_asdf_plugin_{add,update,remove}_<plugin_name>` 请查看 [创建插件](../plugins/create.md) 了解在哪些命令执行之前或之后会运行哪些命令钩子。 ## 环境变量 设置环境变量会因系统和 Shell 的不同而有所差异。默认位置取决于安装位置和方法(Git 克隆、Homebrew、AUR)。 环境变量通常应在加载 `asdf.sh`/`asdf.fish` 等文件之前设置。对于 Elvish,应在 `use asdf` 之前设置。 以下内容描述了在 Bash Shell 中的使用方法。 ### `ASDF_CONFIG_FILE` `.asdfrc` 配置文件的路径。可以设置为任何位置。必须是绝对路径。 - 如果未设置:将使用 `$HOME/.asdfrc`。 - 使用方法:`export ASDF_CONFIG_FILE=/home/john_doe/.config/asdf/.asdfrc` ### `ASDF_TOOL_VERSIONS_FILENAME` 用于存储工具名称和版本的文件名。可以是任何合法的文件名。通常不建议设置这个值,除非你希望忽略 `.tool-versions` 文件。 - 如果未设置:将使用 `.tool-versions`。 - 使用方法:`export ASDF_TOOL_VERSIONS_FILENAME=tool_versions` ### `ASDF_DIR` `asdf` 核心脚本的位置。可以设置为任何位置,必须是绝对路径。 - 如果未设置,将使用 `bin/asdf` 可执行文件的父目录。 - 使用方法:`export ASDF_DIR=/home/john_doe/.config/asdf` ### `ASDF_DATA_DIR` `asdf` 安装插件、垫片和工具版本的位置,可以设置为任何位置,必须是绝对路径。 - 如果未设置:将使用 `$HOME/.asdf` 如果存在,或者 `ASDF_DIR` 的值。 - 使用方法:`export ASDF_DATA_DIR=/home/john_doe/.asdf` ### `ASDF_CONCURRENCY` 编译源代码时使用的 CPU 核心数。如果设置了这个值,它将优先于 asdf 配置中的 `concurrency` 值。 - 如果未设置:将使用 asdf 配置中的 `concurrency` 值。 - 使用方法:`export ASDF_CONCURRENCY=32` ## 全配置样例 按照以下简单的 asdf 配置: - 使用 Bash Shell - 安装位置为 `$HOME/.asdf` - 通过 Git 安装 - **未**设置任何环境变量 - **没有**自定义的 `.asdfrc` 文件 将会产生以下结果: | 配置 | 值 | 如何计算 | | :------------------------------------ | :--------------- | :-------------------------------------------------------------------------------------------------------------------- | | 配置文件位置 | `$HOME/.asdfrc` | `ASDF_CONFIG_FILE` 是空的,所以请使用 `$HOME/.asdfrc` | | 默认工具版本声明文件名 | `.tool-versions` | `ASDF_TOOL_VERSIONS_FILENAME` 是空的,所以请使用 `.tool-versions` | | asdf 目录 | `$HOME/.asdf` | `ASDF_DIR` 是空的,所以请使用 `bin/asdf` 的父目录 | | asdf 数据目录 | `$HOME/.asdf` | `ASDF_DATA_DIR` 是空的,所以请使用 `$HOME/.asdf` 因为 `$HOME` 存在 | | concurrency | `auto` | `ASDF_CONCURRENCY` 是空的,所以依赖于 [默认配置](https://github.com/asdf-vm/asdf/blob/master/defaults) 的 `concurrency` 值 | | legacy_version_file | `no` | 没有自定义 `.asdfrc`,所以请使用 [默认配置](https://github.com/asdf-vm/asdf/blob/master/defaults) | | use_release_candidates | `no` | 没有自定义 `.asdfrc`,所以请使用 [默认配置](https://github.com/asdf-vm/asdf/blob/master/defaults) | | always_keep_download | `no` | 没有自定义 `.asdfrc`,所以请使用 [默认配置](https://github.com/asdf-vm/asdf/blob/master/defaults) | | plugin_repository_last_check_duration | `60` | 没有自定义 `.asdfrc`,所以请使用 [默认配置](https://github.com/asdf-vm/asdf/blob/master/defaults) | | disable_plugin_short_name_repository | `no` | 没有自定义 `.asdfrc`,所以请使用 [默认配置](https://github.com/asdf-vm/asdf/blob/master/defaults) | ================================================ FILE: docs/zh-hans/manage/core.md ================================================ # 核心 核心 `asdf` 命令列表很小,但可以促进很多工作流。 ## 安装和配置 请查看 [快速上手](/zh-hans/guide/getting-started.md) 了解更多详情。 ## Exec ```shell asdf exec <command> [args...] ``` 执行当前版本的命令垫片。 <!-- TODO: expand on this with example --> ## Env ```shell asdf env <command> [util] ``` <!-- TODO: expand on this with example --> ## Info ```shell asdf info ``` 用于打印操作系统、Shell 和 `asdf` 调试信息的辅助命令。在报告 bug 时需要共享这些信息。 ## Reshim ```shell asdf reshim <name> <version> ``` 这将为某个包的当前版本重新创建垫片。默认情况下,垫片是在某个工具安装的过程中由插件创建。一些工具像 [npm 命令行](https://docs.npmjs.com/cli/) 允许全局安装可执行程序,比如使用 `npm install -g yarn` 命令安装 [Yarn](https://yarnpkg.com/)。因为这个可执行程序不是通过插件生命周期安装的,所以还没有对应的垫片存在。`asdf reshim nodejs <version>` 命令将会强制重新计算任何新可执行程序的垫片,类似 `nodejs` 的 `versions` 版本下的 `yarn`。 ## Shim-versions ```shell asdf shimversions <command> ``` 列举为命令提供垫片的插件和版本。 例如,[Node.js](https://nodejs.org/) 附带了两个可执行程序,`node` 和 `npm`。当使用 [`asdf-nodejs`](https://github.com/asdf-vm/asdf-nodejs/) 插件安装了这些工具的很多版本时,执行`shimversions` 命令会返回: ```shell ➜ asdf shimversions node nodejs 14.8.0 nodejs 14.17.3 nodejs 16.5.0 ``` ```shell ➜ asdf shimversions npm nodejs 14.8.0 nodejs 14.17.3 nodejs 16.5.0 ``` ## 更新 请使用与安装 `asdf` 相同的方法进行更新。`asdf` 的最新版本显示在本页面的右上角。 ## 卸载 根据以下步骤卸载 `asdf`: ::: details Bash & Git 1. 在 `~/.bashrc` 配置文件中移除生效 `asdf.sh` 和补全功能的行: ```shell . "$HOME/.asdf/asdf.sh" . "$HOME/.asdf/completions/asdf.bash" ``` 2. 移除 `$HOME/.asdf` 目录: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 3. 执行以下命令移除 `asdf` 所有配置文件: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Bash & Git (macOS) 1. 在 `~/.bash_profile` 配置文件中移除生效 `asdf.sh` 和补全功能的行: ```shell . "$HOME/.asdf/asdf.sh" . "$HOME/.asdf/completions/asdf.bash" ``` 2. 移除 `$HOME/.asdf` 目录: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 3. 执行以下命令移除 `asdf` 所有配置文件: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Bash & Homebrew 1. 在 `~/.bashrc` 配置文件中移除生效 `asdf.sh` 和补全功能的行: ```shell . $(brew --prefix asdf)/libexec/asdf.sh . $(brew --prefix asdf)/etc/bash_completion.d/asdf.bash ``` 补全功能可能已经如 [Homebrew 的指南](https://docs.brew.sh/Shell-Completion#configuring-completions-in-bash) 那样配置了,因此请按照他们的指南找出要删除的内容。 2. 用包管理器卸载: ```shell brew uninstall asdf --force ``` 3. 执行以下命令移除 `asdf` 所有配置文件: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Bash & Homebrew (macOS) 如果你正在使用 **macOS Catalina 以及更新版本**,默认的 shell 已经变成了 **ZSH**。如果你在 `~/.bash_profile` 文件中找不到任何配置,则可能位于 `~/.zshrc` 中。在这种情况下,请按照 ZSH 指南进行操作。 1. 在 `~/.bash_profile` 配置文件中移除生效 `asdf.sh` 和补全功能的行: ```shell . $(brew --prefix asdf)/libexec/asdf.sh . $(brew --prefix asdf)/etc/bash_completion.d/asdf.bash ``` 补全功能可能已经如 [Homebrew 的指南](https://docs.brew.sh/Shell-Completion#configuring-completions-in-bash) 那样配置了,因此请按照他们的指南找出要删除的内容。 2. 用包管理器卸载: ```shell brew uninstall asdf --force ``` 3. 执行以下命令移除 `asdf` 所有配置文件: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Bash & Pacman 1. 在 `~/.bashrc` 配置文件中移除生效 `asdf.sh` 和补全功能的行: ```shell . /opt/asdf-vm/asdf.sh ``` 2. 用包管理器卸载: ```shell pacman -Rs asdf-vm ``` 3. 移除 `$HOME/.asdf` 目录: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 4. 执行以下命令移除 `asdf` 所有配置文件: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Fish & Git 1. 在 `~/.config/fish/config.fish` 配置文件中移除生效 `asdf.fish` 的行: ```shell source ~/.asdf/asdf.fish ``` 以及使用以下命令移除补全功能: ```shell rm -rf ~/.config/fish/completions/asdf.fish ``` 2. 移除 `$HOME/.asdf` 目录: ```shell rm -rf (string join : -- $ASDF_DATA_DIR $HOME/.asdf) ``` 3. 执行以下命令移除 `asdf` 所有配置文件: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Fish & Homebrew 1. 在 `~/.config/fish/config.fish` 配置文件中移除生效 `asdf.fish` 的行: ```shell source "(brew --prefix asdf)"/libexec/asdf.fish ``` 2. 用包管理器卸载: ```shell brew uninstall asdf --force ``` 3. 执行以下命令移除 `asdf` 所有配置文件: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Fish & Pacman 1. 在 `~/.config/fish/config.fish` 配置文件中移除生效 `asdf.fish` 的行: ```shell source /opt/asdf-vm/asdf.fish ``` 2. 用包管理器卸载: ```shell pacman -Rs asdf-vm ``` 3. 移除 `$HOME/.asdf` 目录: ```shell rm -rf (string join : -- $ASDF_DATA_DIR $HOME/.asdf) ``` 4. 执行以下命令移除 `asdf` 所有配置文件: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Elvish & Git 1. 在 `~/.config/elvish/rc.elv` 配置文件中移除使用 `asdf` 模块的行: ```shell use asdf _asdf; var asdf~ = $_asdf:asdf~ set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~ ``` 以及使用以下命令卸载 `asdf` 模块: ```shell rm -f ~/.config/elvish/lib/asdf.elv ``` 2. 移除 `$HOME/.asdf` 目录: ```shell if (!=s $E:ASDF_DATA_DIR "") { rm -rf $E:ASDF_DATA_DIR } else { rm -rf ~/.asdf } ``` 3. 执行以下命令移除 `asdf` 所有配置文件: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Elvish & Homebrew 1. 在 `~/.config/elvish/rc.elv` 配置文件中移除使用 `asdf` 模块的行: ```shell use asdf _asdf; var asdf~ = $_asdf:asdf~ set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~ ``` 以及使用以下命令卸载 `asdf` 模块: ```shell rm -f ~/.config/elvish/lib/asdf.elv ``` 2. 用包管理器卸载: ```shell brew uninstall asdf --force ``` 3. 执行以下命令移除 `asdf` 所有配置文件: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details Elvish & Pacman 1. 在 `~/.config/elvish/rc.elv` 配置文件中移除使用 `asdf` 模块的行: ```shell use asdf _asdf; var asdf~ = $_asdf:asdf~ set edit:completion:arg-completer[asdf] = $_asdf:arg-completer~ ``` 以及使用以下命令卸载 `asdf` 模块: ```shell rm -f ~/.config/elvish/lib/asdf.elv ``` 2. 用包管理器卸载: ```shell pacman -Rs asdf-vm ``` 3. 移除 `$HOME/.asdf` 目录: ```shell if (!=s $E:ASDF_DATA_DIR "") { rm -rf $E:ASDF_DATA_DIR } else { rm -rf ~/.asdf } ``` 4. 执行以下命令移除 `asdf` 所有配置文件: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details ZSH & Git 1. 在 `~/.zshrc` 配置文件中移除生效 `asdf.sh` 和补全功能的行: ```shell . "$HOME/.asdf/asdf.sh" # ... fpath=(${ASDF_DIR}/completions $fpath) autoload -Uz compinit compinit ``` **或者** ZSH 框架插件(如果用了的话) 2. 移除 `$HOME/.asdf` 目录: ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 3. 执行以下命令移除 `asdf` 所有配置文件: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details ZSH & Homebrew 1. 在 `~/.zshrc` 配置文件中移除生效 `asdf.sh` 的行: ```shell . $(brew --prefix asdf)/libexec/asdf.sh ``` 2. 用包管理器卸载: ```shell brew uninstall asdf --force && brew autoremove ``` 3. 执行以下命令移除 `asdf` 所有配置文件: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: ::: details ZSH & Pacman 1. 在 `~/.zshrc` 配置文件中移除生效 `asdf.sh` 的行: ```shell . /opt/asdf-vm/asdf.sh ``` 2. 用包管理器卸载: ```shell pacman -Rs asdf-vm ``` 3. 移除 `$HOME/.asdf` 目录 ```shell rm -rf "${ASDF_DATA_DIR:-$HOME/.asdf}" ``` 4. 执行以下命令移除 `asdf` 所有配置文件: ```shell rm -rf "$HOME/.tool-versions" "$HOME/.asdfrc" ``` ::: 恭喜你完成了 🎉 ================================================ FILE: docs/zh-hans/manage/dependencies.md ================================================ # 依赖 此列表适用于 asdf 版本 0.16.0 及更高版本。旧版本的 asdf 具有额外的依赖。 asdf 本身需要安装以下内容: * Bash 版本 `3.2.48` * Git 版本 `1.7.7.2` ::: tip 注意 请注意,asdf 插件可能需要在使用前安装额外的程序。请阅读插件文档,并确保已安装所有插件所需的依赖,然后再安装插件。 ::: ## 安装 如果需要手动安装 asdf 的依赖,请根据操作系统运行以下命令。 <!--@include: @/zh-hans/parts/install-dependencies-cmds.md--> ::: tip 注意 取决于你的系统配置,可能会需要 `sudo`。 ::: ================================================ FILE: docs/zh-hans/manage/plugins.md ================================================ # 插件 插件告诉 `asdf` 如何处理不同的工具,如 Node.js、 Ruby、 Elixir 等。 请参考 [创建插件](/zh-hans/plugins/create.md) 了解用于支持更多工具的插件 API。 ## 添加 通过 Git URL 地址添加插件: ```shell asdf plugin add <name> <git-url> # asdf plugin add elm https://github.com/vic/asdf-elm ``` 或者通过插件存储库中的缩写添加插件: ```shell asdf plugin add <name> # asdf plugin add erlang ``` ::: tip 建议 推荐独立于缩写存储库的、更长的 `git-url` 方法。 ::: ## 列举已安装 ```shell asdf plugin list # asdf plugin list # java # nodejs ``` ```shell asdf plugin list --urls # asdf plugin list # java https://github.com/halcyon/asdf-java.git # nodejs https://github.com/asdf-vm/asdf-nodejs.git ``` ## 列举缩写存储库中的所有插件 ```shell asdf plugin list all ``` 请参考 [插件缩写索引](https://github.com/asdf-vm/asdf-plugins) 了解插件的完整缩写列表。 ## 更新 ```shell asdf plugin update --all ``` 如果你想要更新特定的包,如下所示。 ```shell asdf plugin update <name> # asdf plugin update erlang ``` 这种更新方式将会获取插件存储库的 _源代码_ 的 _默认分支_ 的 _最新提交_。版本化的插件和更新正在开发中 ([#916](https://github.com/asdf-vm/asdf/pull/916))。 ## 移除 ```bash asdf plugin remove <name> # asdf plugin remove erlang ``` 移除一个插件将会移除该插件安装的所有工具。这可以当作是清理/修剪工具的许多未使用版本的简单方法。 ## 同步缩写存储库 缩写存储库将同步到你的本地计算机并定期刷新。这个周期由以下方法确定: - 命令触发的同步事件: - `asdf plugin add <name>` - `asdf plugin list all` - 如果配置选项 `disable_plugin_short_name_repository` 设置为 `yes`,那么同步操作将提前终止。请查看 [asdf 配置文档](/zh-hans/manage/configuration.md) 了解更多。 - 如果在过去的 `X` 分钟内没有同步,则进行同步。 - `X` 默认是 `60`,但可以通过在 `.asdfrc` 文件中配置 `plugin_repository_last_check_duration` 选项来进行配置。请查看 [asdf 配置文档](/zh-hans/manage/configuration.md) 了解更多。 ================================================ FILE: docs/zh-hans/manage/versions.md ================================================ # 版本 ## 安装版本 ```shell asdf install <name> <version> # asdf install erlang 17.3 ``` 如果一个插件支持从源代码下载和编译,你可以指定 `ref:foo`,其中 `foo` 是特定的分支、标签或者提交。卸载该版本时,你也需要使用相同的名称和引用。 ## 安装最新稳定版本 ```shell asdf install <name> latest # asdf install erlang latest ``` 安装给定字符串开头的最新稳定版本。 ```shell asdf install <name> latest:<version> # asdf install erlang latest:17 ``` ## 列举已安装版本 ```shell asdf list <name> # asdf list erlang ``` 筛选出以给定字符串开头的版本。 ```shell asdf list <name> <version> # asdf list erlang 17 ``` ## 列举所有可用版本 ```shell asdf list all <name> # asdf list all erlang ``` 筛选出以给定字符串开头的版本。 ```shell asdf list all <name> <version> # asdf list all erlang 17 ``` ## 显示最新稳定版本 ```shell asdf latest <name> # asdf latest erlang ``` 显示以给定字符串开头的最新稳定版本。 ```shell asdf latest <name> <version> # asdf latest erlang 17 ``` ## 设置版本 #### 通过 `.tool-versions` 文件 ```shell asdf set [flags] <name> <version> [<version>...] # asdf set elixir 1.2.4 # set in current dir # asdf set -u elixir 1.2.4 # set in .tool-versions file in home directory # asdf set -p elixir 1.2.4 # set in existing .tool-versions file in a parent dir asdf set <name> latest[:<version>] # asdf set elixir latest ``` `asdf set` 将版本信息写入当前目录下的 `.tool-versions` 文件中,若该文件不存在则自动创建。此功能仅为方便使用而设。你可以将其视为执行 `echo "<tool> <version>" > .tool-versions` 的操作。 使用 `-u`/`--home` 选项时,`asdf set` 将写入位于 `$HOME` 目录下的 `.tool-versions` 文件,如果该文件不存在则创建它。 使用 `-p`/`--parent` 选项时,`asdf set` 会寻找在当前目录的最近父目录的 `.tool-versions` 文件进行操作。 #### 通过环境变量 在确定版本时,系统会查询符合模式的环境变量 `ASDF_${TOOL}_VERSION`。该版本格式与 `.tool-versions` 文件中支持的格式一致。如果设置了该环境变量,其值将覆盖任何 `.tool-versions` 文件中为该工具设置的版本。例如: ```shell export ASDF_ELIXIR_VERSION=1.18.1 ``` 这将会告诉 asdf 在当前会话中使用 Elixir `1.18.1`。 :::warning 警告 由于这是一个环境变量,它仅在设置的位置生效。其他正在运行的 shell 会话仍将使用在 `.tool-versions` 文件中设置的版本。 请查看 [配置部分](/zh-hans/manage/configuration.md) 的 `.tool-versions` 文件了解更多详情。 ::: 下面的示例在版本为 `1.4.0` 的 Elixir 项目上运行测试。 ```shell ASDF_ELIXIR_VERSION=1.4.0 mix test ``` ## 回退到系统版本 要使用工具 `<name>` 的系统版本而非 asdf 管理版本,你可以将工具的版本设置为 `system`。 使用 `asdf set` 命令或如上文 [设置版本](#设置版本) 部分所述的环境变量来设置系统。 ```shell asdf set <name> system # asdf set python system ``` ## 显示当前版本 ```shell asdf current # asdf current # erlang 17.3 /Users/kim/.tool-versions # nodejs 6.11.5 /Users/kim/cool-node-project/.tool-versions asdf current <name> # asdf current erlang # erlang 17.3 /Users/kim/.tool-versions ``` ## 卸载版本 ```shell asdf uninstall <name> <version> # asdf uninstall erlang 17.3 ``` ## 垫片(Shims) 当 asdf 安装一个包时,它会在 `$ASDF_DATA_DIR/shims` 目录(默认为 `~/.asdf/shims`)中为该包中的每个可执行程序创建垫片。这个位于 `$PATH` 中(通过 `asdf.sh`、 `asdf.fish` 等等实现)的目录是已安装程序在环境中可用的方式。 垫片本身是非常简单的包装器,它 `exec` (执行)一个辅助程序 `asdf exec`,向其传递插件的名称和垫片正在包装的已安装包中的可执行程序的路径。 `asdf exec` 辅助程序确定要使用的软件包版本(比如在 `.tool-versions` 文件中指定,通过 `asdf local ...` 或者 `asdf global ...` 命令选择)、软件包安装目录中的可执行程序的最终路径(这可以在插件中通过 `exec-path` 回调来操作)以及要在其中执行的环境(也由插件 - `exec-env` 脚本提供),最后完成执行。 ::: warning 注意 因为此系统使用 `exec` 调用,所以软件包中的任何脚本如果要由 shell 生效而不是执行的脚本都需要直接访问,而不是通过垫片包装器进行访问。两个 `asdf` 命令:`which` 和 `where` 可以通过返回已安装软件包的路径来帮助解决这个问题。 ::: ```shell # 返回当前版本中主要可执行程序的路径 source $(asdf which ${PLUGIN})/../script.sh # 返回软件包安装目录的路径 source $(asdf where ${PLUGIN})/bin/script.sh ``` ### 绕过 asdf 垫片 如果由于某种原因,你希望绕过 asdf 垫片,或者希望在进入项目目录时自动设置环境变量,则 [asdf-direnv](https://github.com/asdf-community/asdf-direnv) 插件可能会有所帮助。请务必查看其 README 文件了解更多详情。 ================================================ FILE: docs/zh-hans/more/community-projects.md ================================================ # Community Projects Here are some community projects related to `asdf`: - [asdf-community](https://github.com/asdf-community): A collaborative, community-driven project for long-term maintenance of asdf plugins. - [asdf dev container](https://github.com/iloveitaly/asdf-devcontainer): A [GitHub Dev Container](https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/introduction-to-dev-containers) supporting asdf managed tools in GitHub Codespaces. ::: warning Note asdf core do not own these projects or their code. asdf core are not responsible for the quality or security as they relate to those listed here. ::: ================================================ FILE: docs/zh-hans/more/faq.md ================================================ # FAQ 以下是 `asdf` 相关的一些常见问题。 ## 支持 WSL1 吗? WSL1 ([Windows Subsystem for Linux 1](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux#WSL_1)) 不受官方支持。`asdf` 的某些方面可能无法正常工作。我们不打算添加对 WSL1 的官方支持。 ## 支持 WSL2 吗? WSL2 ([Windows Subsystem for Linux 2](https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux#WSL_2)) 应该作为你选择的 WSL 发行版来使用本设置和依赖说明。 重要的是,只有当前工作目录是 Unix 驱动器而不是绑定的 Windows 驱动器时,WSL2 _才能_ 正常工作。 当 Github Actions 上提供主机运行器支持时,我们打算在 WSL2 上运行测试套件。(Github Actions 目前还未提供 WSL2 支持) ## 新安装的可执行程序无法运行? > 我执行了 `npm install -g yarn` 命令,但是之后不能运行 `yarn` 命令。这是为什么? `asdf` 使用 [垫片](<https://zh.wikipedia.org/wiki/垫片_(程序设计)>) 来管理可执行程序。插件所安装的那些命令会自动创建垫片,而通过 `asdf` 管理工具安装过的可执行程序则需要通知 `asdf` 创建垫片的需要。在这个例子中,为 [Yarn](https://yarnpkg.com/) 创建一个垫片即可。请查看 [`asdf reshim` 命令文档](/zh-hans/manage/core.md#reshim) 了解更多。 ## Shell 没有检测到新安装的垫片? 如果 `asdf reshim` 没有解决你的问题,那么很有可能是在 `asdf.sh` 或者 `asdf.fish` 的生效不在你的 Shell 配置文件(`.bash_profile`、`.zshrc`、`config.fish` 等等)的**下方**。这需要你在设置你的 `$PATH` **之后**和生效你的框架(oh-my-zsh 等等)(如果有的话)**之后**再生效。 ## 为什么不能在 `.tool-versions` 文件中使用 `latest` 版本? asdf 必须始终使用当前目录的每个工具的精确版本,不允许使用版本范围或特殊值(如 `latest`)。这确保 asdf 在不同时间和不同机器上以确定性和一致性方式运行。像 `latest` 这样的特殊版本会随时间变化,并且如果在不同时间运行 `asdf install`,不同机器上的版本可能会有所不同。因此,它可以在 asdf 命令如 `asdf set <tool> latest` 中使用,但在 `.tool-versions` 文件中是被禁止的。 可将 `.tool-versions` 文件看成 `Gemfile.lock` 或者 `package-lock.json`。该文件包含项目依赖的每个工具的精确版本。 需要注意的是,`system` 版本在 `.tool-versions` 文件中是允许的,且在使用时可能解析为不同版本。这是一个特殊值,可有效禁用指定目录下特定工具的 asdf 功能。 请查看 https://github.com/asdf-vm/asdf/issues/1012 了解更多。 ## 为什么不能在 `.tool-versions` 文件中使用版本范围? 与上述关于使用 `latest` 的问题类似。如果指定了版本范围,asdf 将可以自由选择该范围内的任何已安装版本。这可能导致不同机器上出现不同行为,因为它们可能安装了不同版本。asdf 的设计意图是完全确定性的,即相同的 `.tool-versions` 文件在不同时间和不同计算机上应生成完全相同的环境。 请查看 https://github.com/asdf-vm/asdf-nodejs/issues/235#issuecomment-885809776 了解更多。 ## 为什么与我使用的插件完全无关的命令会被 asdf 生成垫片? **asdf 只会为其管理的可执行文件生成垫片**。例如,如果你使用 Ruby 插件,那么你可能会看到 `ruby` 和 `irb` 等命令被垫片替换,以及你安装的 Ruby 包中包含的其他可执行文件。 如果你看到一个意料之外的垫片,很可能是因为你通过 asdf 管理工具安装了一个包,而该包提供了该可执行文件。 当可执行文件与系统中已存在的可执行文件名称相同,这种情况会令人意外。[部分用户报告称](https://github.com/asdf-vm/asdf/issues/584) 某个 Node.JS 包提供了自己的 `which` 命令版本。这导致 asdf 为其创建了垫片,并替换了操作系统中已存在的 `which` 命令版本。在这种情况下,最好定位引入可执行文件的包并将其移除。`asdf which <command>` 命令可帮助你确定问题可执行文件的位置,从而判断是哪一个包添加了可执行文件。 请查看 https://github.com/asdf-vm/asdf/issues/584 https://github.com/asdf-vm/asdf/issues/1653 了解更多。 ================================================ FILE: docs/zh-hans/more/thanks.md ================================================ # 致谢 asdf 作者和贡献者的感谢页! ## 作者 我 ([@HashNuke](https://github.com/HashNuke)),高烧、感冒、咳嗽。 版权 2014 直到时间尽头 ([MIT License](https://github.com/asdf-vm/asdf/blob/master/LICENSE)) ## 维护者 - [@HashNuke](https://github.com/HashNuke) - [@danhper](https://github.com/danhper) - [@Stratus3D](https://github.com/Stratus3D) - [@vic](https://github.com/vic) - [@jthegedus](https://github.com/jthegedus) ## 贡献者 请查看 Github 上的 [贡献者名单](https://github.com/asdf-vm/asdf/graphs/contributors) 🙏 了解更多。 ================================================ FILE: docs/zh-hans/parts/install-dependencies-cmds.md ================================================ | 操作系统 | 包管理器 | 命令 | | ----- | --------------- | ---------------------------------- | | linux | Aptitude | `apt install git bash` | | linux | DNF | `dnf install git bash` | | linux | Pacman | `pacman -S git bash` | | linux | Zypper | `zypper install git bash` | | macOS | Homebrew | `brew install coreutils git bash` | | macOS | Spack | `spack install coreutils git bash` | ================================================ FILE: docs/zh-hans/parts/install-dependencies.md ================================================ ##### 安装依赖 asdf 主要依赖 `git`。 这是一份 _非穷举_ 的命令清单用于运行 _你的_ 包管理器(有些可能在后面的步骤自动安装这些工具)。 <!--@include: @/zh-hans/parts/install-dependencies-cmds.md--> ::: tip 注意 取决于你的系统配置,可能会需要 `sudo`。 ::: 请查看 [依赖](/zh-hans/manage/dependencies) 页面了解更多。 ================================================ FILE: docs/zh-hans/plugins/create.md ================================================ # 创建插件 插件是一个包含一些可执行脚本的 Git 存储库,用于支持对某种语言/工具进行版本控制。这些脚本由 asdf 通过命令运行,以支持诸如 `asdf list-all <name>`、`asdf install <name> <version>` 等功能。 ## 快速入门 创建自己的插件有两种方式: 1. 使用 [asdf-vm/asdf-plugin-template](https://github.com/asdf-vm/asdf-plugin-template) 仓库来 [生成](https://github.com/asdf-vm/asdf-plugin-template/generate) 一个插件仓库(命名为 `asdf-<tool_name>`),其中已实现默认脚本。生成后,克隆该仓库并运行 `setup.bash` 脚本以交互式更新模版。 2. 创建名为 `asdf-<tool_name>` 的仓库,并实现文档中列出的所需脚本。 ### 插件脚本的黄金规则 - 脚本**不应**调用其他 `asdf` 命令 - 保持 Shell 工具/命令的依赖列表简短 - 避免使用非便携式工具或命令标志。例如,`sort -V`。请参考 asdf 核心 [禁止命令列表](https://github.com/asdf-vm/asdf/blob/master/test/banned_commands.bats)。 ## 脚本概述 可从 asdf 调用的所有脚本的完整列表。 | 脚本 | 描述 | | :---------------------------------------------------------------------------------------------------- |:---------------------------------------| | [bin/list-all](#bin-list-all) <Badge type="tip" text="必要" vertical="middle" /> | 列出所有可安装的版本 | | [bin/download](#bin-download) <Badge type="warning" text="推荐" vertical="middle" /> | 下载指定版本的源代码或二进制文件 | | [bin/install](#bin-install) <Badge type="tip" text="必要" vertical="middle" /> | 安装指定版本 | | [bin/latest-stable](#bin-latest-stable) <Badge type="warning" text="推荐" vertical="middle" /> | 列出指定工具的最新稳定版本 | | [bin/help.overview](#bin-help.overview) | 输出插件及工具的通用描述 | | [bin/help.deps](#bin-help.deps) | 输出按操作系统分类的依赖项列表 | | [bin/help.config](#bin-help.config) | 输出插件或工具的配置信息 | | [bin/help.links](#bin-help.links) | 输出插件或工具的链接列表 | | [bin/list-bin-paths](#bin-list-bin-paths) | 列出包含二进制文件的目录的相对路径以创建垫片 | | [bin/exec-env](#bin-exec-env) | 为运行二进制文件准备环境 | | [bin/exec-path](#bin-exec-path) | 输出工具某个版本的可执行路径 | | [bin/uninstall](#bin-uninstall) | 卸载工具的特定版本Uninstall | | [bin/list-legacy-filenames](#bin-list-legacy-filenames) | 输出旧版本文件的文件名:`.ruby-version` | | [bin/parse-legacy-file](#bin-parse-legacy-file) | 用于解析旧版本文件的自定义解析器 | | [bin/post-plugin-add](#bin-post-plugin-add) | 在插件添加后执行的钩子 | | [bin/post-plugin-update](#bin-post-plugin-update) | 插件更新后执行的钩子 | | [bin/pre-plugin-remove](#bin-pre-plugin-remove) | 插件删除前执行的钩子 | 要查看哪些命令调用了哪些脚本,请参考每个脚本的详细文档。 ## 环境变量概述 所有脚本中使用的环境变量完整列表。 | 环境变量 | 描述 | | :----------------------- |:-----------------------------------------------| | `ASDF_INSTALL_TYPE` | `version` 或 `ref` | | `ASDF_INSTALL_VERSION` | 完整版本号或 Git 引用,取决于 `ASDF_INSTALL_TYPE` | | `ASDF_INSTALL_PATH` | 工具 _应_ 安装或 _已_ 安装的路径 | | `ASDF_CONCURRENCY` | 编译源代码时使用的核心数。用于设置 `make -j` | | `ASDF_DOWNLOAD_PATH` | `bin/download` 下载源代码或二进制文件的路径 | | `ASDF_PLUGIN_PATH` | 插件的安装路径 | | `ASDF_PLUGIN_SOURCE_URL` | 插件的来源 URL | | `ASDF_PLUGIN_PREV_REF` | 插件仓库的上一版本 `git-ref` | | `ASDF_PLUGIN_POST_REF` | 插件仓库的更新版本 `git-ref` | | `ASDF_CMD_FILE` | 解析为被引用的文件的完整路径 | ::: tip 注意 **并非所有环境变量在所有脚本中都可以用。** 请查看下方每个脚本的文档,以了解其可用的环境变量。 ::: ## 必要的脚本 ### `bin/list-all` <Badge type="tip" text="必要" vertical="middle" /> **描述** 列出所有可安装的版本。 **输出格式** 必须打印一个以**空格分隔**的版本列表字符串。例如: ```txt 1.0.1 1.0.2 1.3.0 1.4 ``` 最新版本应放在最后。 asdf core 会将每个版本单独打印在单独的行上,可能导致部分版本超出屏幕范围。 **排序** 如果版本是从网站的发布页面获取的,建议保持提供的顺序,因为它们通常已经按正确顺序排列。如果版本顺序相反,通过 `tac` 管道处理应 能解决问题。 如果排序不可避免,`sort -V` 不可移植,因此我们建议: - [使用 Git 的排序功能](https://github.com/asdf-vm/asdf-plugin-template/blob/main/template/lib/utils.bash) (需要 Git >= `v2.18.0`) - [编写自定义排序方法](https://github.com/vic/asdf-idris/blob/master/bin/list-all#L6) (需要 `sed`、`sort` 和 `awk`) **脚本可用的环境变量** 此脚本未提供任何环境变量。 **调用此脚本的命令** - `asdf list all <name> [version]` - `asdf list all nodejs`:列出此脚本返回的所有版本,每个版本占一行。 - `asdf list all nodejs 18`:列出此脚本返回的所有版本,每个版本占一行,并应用过滤器匹配以 `18` 开头的任何版本。 **asdf 核心调用签名** 未提供参数。 ```bash “${plugin_path}/bin/list-all” ``` --- ### `bin/download` <Badge type="tip" text="必要" vertical="middle" /> **描述** 将特定版本的工具的源代码或二进制文件下载到指定位置。 **实现细节** - 脚本必须将源代码或二进制文件下载到由 `ASDF_DOWNLOAD_PATH` 指定的目录中。 - 仅应将解压后的源代码或二进制文件放置在 `ASDF_DOWNLOAD_PATH` 目录中。 - 失败时,不应将任何文件放置在 `ASDF_DOWNLOAD_PATH` 中。 - 成功时应以 `0` 退出。 - 失败时应以非零状态退出。 **旧插件** 尽管此脚本被标记为所有插件的 _必需_,但对于在引入此脚本之前存在的“旧”插件,它是 _可选_ 的。 如果此脚本缺失,asdf 将假设 `bin/install` 脚本存在,并会下载**并**安装该版本。 所有插件必须包含此脚本,因为对旧插件的支持最终将被移除。 **脚本可用的环境变量** - `ASDF_INSTALL_TYPE`:`version` 或 `ref` - `ASDF_INSTALL_VERSION`: - 如果 `ASDF_INSTALL_TYPE=version`,则为完整版本号。 - 如果 `ASDF_INSTALL_TYPE=ref`,则为 Git 引用(标签/提交/分支)。 - `ASDF_INSTALL_PATH`:工具 _已_ 安装或 _应_ 安装的路径。 - `ASDF_DOWNLOAD_PATH`:源代码或二进制文件的下载路径。 **调用此脚本的命令** - `asdf install <tool> [version]]` - `asdf install <tool> latest[:version]` - `asdf install nodejs 18.0.0`:下载 Node.js 版本 `18.0.0` 的源代码或二进制文件, 并将它们放置在 `ASDF_DOWNLOAD_PATH` 目录中。然后运行 `bin/install` 脚本。 **asdf 核心的调用签名** 未提供参数。 ```bash "${plugin_path}"/bin/download ``` --- ### `bin/install` <Badge type="tip" text="必要" vertical="middle" /> **描述** 将特定版本的工具安装到指定位置。 **实现细节** - 脚本应将指定版本安装到路径 `ASDF_INSTALL_PATH` 中。 - 默认情况下,`$ASDF_INSTALL_PATH/bin` 目录下的文件将自动垫片(shims)。此行为可通过可选的 [bin/list-bin-paths](#bin-list-bin-paths) 脚本进行自定义。 - 成功应以 `0` 退出。 - 失败应以非零状态退出。 - 为避免 TOCTOU(Time-of-Check-to-Time-of-Use)问题,确保脚本仅在工具的构建和安装被视为成功后,才将文件放置在 `ASDF_INSTALL_PATH` 中。 **旧插件** 如果 `bin/download` 脚本不存在,该脚本应下载并安装指定版本。 为了与 `0.7._` 之前和 `0.8._` 之后的 asdf 核心版本兼容,检查 `ASDF_DOWNLOAD_PATH` 环境变量的存在。如果设置了该变量,则假设 `bin/download` 脚本已下载该版本,否则在 `bin/install` 脚本中下载源代码。 **脚本可用的环境变量** - `ASDF_INSTALL_TYPE`:`version` 或 `ref` - `ASDF_INSTALL_VERSION`: - 如果 `ASDF_INSTALL_TYPE=version`,则为完整版本号。 - 如果 `ASDF_INSTALL_TYPE=ref`,则为 Git 引用(标签/提交/分支)。 - `ASDF_INSTALL_PATH`:工具 _已_ 安装或 _应_ 安装的路径。 - `ASDF_CONCURRENCY`:编译源代码时使用的核心数。可用于设置如 `make -j` 之类的标志。 - `ASDF_DOWNLOAD_PATH`:源代码或二进制文件的下载路径。 **调用此脚本的命令** - `asdf install` - `asdf install <tool>` - `asdf install <tool> [version]]` - `asdf install <tool> latest[:version]` - `asdf install nodejs 18.0.0`:在 `ASDF_INSTALL_PATH` 目录中安装 Node.js 版本 `18.0.0`。 **asdf 核心的调用签名** 未提供参数。 ```bash "${plugin_path}"/bin/install ``` ## 可选脚本 ### `bin/latest-stable` <Badge type="warning" text="推荐" vertical="middle" /> **描述** 确定工具的最新稳定版本。如果不存在,asdf 核心将 `tail` `bin/list-all` 的输出,这可能是不希望看到的。 **实现细节** - 脚本应将工具的最新稳定版本打印到标准输出。 - 不稳定版本或发布候选版本应被省略。 - 脚本的第一个参数提供了一个过滤查询。这应用于按版本号或工具提供商过滤输出。 - 例如,来自 [ruby 插件](https://github.com/asdf-vm/asdf-ruby) 的 `asdf list all ruby` 输出列出了来自多个提供商的 Ruby 版本:`jruby`、`rbx` 和 `truffleruby` 等。用户提供的过滤器可由插件用于过滤语义化版本和/或提供商。 ``` > asdf latest ruby 3.2.2 > asdf latest ruby 2 2.7.8 > asdf latest ruby truffleruby truffleruby+graalvm-22.3.1 ``` - 成功应以 `0` 退出。 - 失败应以非零状态退出。 **脚本可用的环境变量** - `ASDF_INSTALL_TYPE`:`version` 或 `ref` - `ASDF_INSTALL_VERSION`: - 若 `ASDF_INSTALL_TYPE=version`,则为完整版本号。 - 若 `ASDF_INSTALL_TYPE=ref`,则为 Git 引用(标签/提交/分支)。 - `ASDF_INSTALL_PATH`:工具 _已_ 安装或 _应_ 安装的路径。 **调用此脚本的命令** - `asdf set <tool> latest`:将工具的版本设置为该工具的最新稳定版本。 - `asdf install <tool> latest`:安装工具的最新版本。 - `asdf latest <tool> [<version>]`:根据可选过滤器输出工具的最新版本。 - `asdf latest --all`:输出由 asdf 管理的所有工具的最新版本及其安装状态。 **asdf 核心的调用签名** 该脚本应接受一个参数,即过滤查询。 ```bash "${plugin_path}"/bin/latest-stable "$query" ``` --- ### `bin/help.overview` **描述** 输出关于插件和所管理工具的通用描述。 **实现细节** - 该脚本是显示插件帮助输出所必需的。 - 不得打印任何标题,因为 asdf 核心会打印标题。 - 输出可以是自由格式文本,但理想情况下应仅包含一个简短段落。 - 不得输出已在 asdf-vm 核心文档中涵盖的任何信息。 - 应根据所安装工具的操作系统和版本进行定制(可选设置环境变量 `ASDF_INSTALL_VERSION` 和 `ASDF_INSTALL_TYPE`)。 - 成功应以 `0` 退出。 - 失败应以非零状态退出。 **脚本可用的环境变量** - `ASDF_INSTALL_TYPE`:`version` 或 `ref` - `ASDF_INSTALL_VERSION`: - 若 `ASDF_INSTALL_TYPE=version`,则为完整版本号。 - 若 `ASDF_INSTALL_TYPE=ref`,则为 Git 引用(标签/提交/分支)。 - `ASDF_INSTALL_PATH`:工具 _已_ 安装或 _应_ 安装的路径。 **调用此脚本的命令** - `asdf help <name> [<version>]`:输出插件和工具的文档 **asdf 核心的调用签名** ```bash "${plugin_path}"/bin/help.overview ``` --- ### `bin/help.deps` **描述** 输出针对操作系统定制的依赖项列表。每个依赖项占一行。 ```bash git curl sed ``` **实现细节** - 该脚本需要 `bin/help.overview` 才能被视为有效输出。 - 应根据操作系统和要安装的工具版本进行定制(可选设置环境变量 `ASDF_INSTALL_VERSION` 和 `ASDF_INSTALL_TYPE`)。 - 成功应以 `0` 退出。 - 失败应以非零状态退出。 **脚本可用的环境变量** - `ASDF_INSTALL_TYPE`:`version` 或 `ref` - `ASDF_INSTALL_VERSION`: - 若 `ASDF_INSTALL_TYPE=version`,则为完整版本号。 - 若 `ASDF_INSTALL_TYPE=ref`,则为 Git 引用(标签/提交/分支)。 - `ASDF_INSTALL_PATH`:工具 _已_ 安装或 _应_ 安装的路径。 **调用此脚本的命令** - `asdf help <name> [<version>]`:输出插件和工具的文档 **asdf 核心的调用签名** ```bash "${plugin_path}"/bin/help.deps ``` --- ### `bin/help.config` **描述** 输出插件和工具所需的任何必要或可选配置。例如,描述安装或编译工具所需的任何环境变量或其他标志。 **实现细节** - 该脚本需要 `bin/help.overview` 才能被视为有效输出。 - 输出可以是自由格式文本。 - 应根据所安装工具的操作系统和版本进行定制(可选设置环境变量 `ASDF_INSTALL_VERSION` 和 `ASDF_INSTALL_TYPE`)。 - 成功应以 `0` 退出。 - 失败应以非零状态退出。 **脚本可用的环境变量** - `ASDF_INSTALL_TYPE`:`version` 或 `ref` - `ASDF_INSTALL_VERSION`: - 若 `ASDF_INSTALL_TYPE=version`,则为完整版本号。 - 若 `ASDF_INSTALL_TYPE=ref`,则为 Git 引用(标签/提交/分支)。 - `ASDF_INSTALL_PATH`:工具 _已_ 安装或 _应_ 安装的路径。 **调用此脚本的命令** - `asdf help <name> [<version>]`:输出插件和工具的文档 **asdf 核心的调用签名** ```bash "${plugin_path}"/bin/help.config ``` --- ### `bin/help.links` **描述** 输出与插件和工具相关的链接列表。每个链接占一行。 ```bash Git Repository: https://github.com/vlang/v Documentation: https://vlang.io ``` **实现细节** - 该脚本需要 `bin/help.overview` 才能被视为有效输出。 - 每行一个链接。 - 格式必须为以下之一: - `<标题>: <链接>` - 或仅 `<链接>` - 应根据所安装工具的操作系统和版本进行调整(可选设置环境变量 `ASDF_INSTALL_VERSION` 和 `ASDF_INSTALL_TYPE`)。 - 成功应以 `0` 退出。 - 失败应以非零状态退出。 **脚本可用的环境变量** - `ASDF_INSTALL_TYPE`:`version` 或 `ref` - `ASDF_INSTALL_VERSION`: - 若 `ASDF_INSTALL_TYPE=version`,则为完整版本号。 - 若 `ASDF_INSTALL_TYPE=ref`,则为 Git 引用(标签/提交/分支)。 - `ASDF_INSTALL_PATH`:工具 _已_ 安装或 _应_ 安装的路径。 **调用此脚本的命令** - `asdf help <name> [<version>]`:输出插件和工具的文档 **asdf 核心的调用签名** ```bash "${plugin_path}"/bin/help.links ``` --- ### `bin/list-bin-paths` **描述** 列出包含指定版本工具可执行文件的目录。 **实现细节** - 如果该脚本不存在,asdf 将搜索 `“${ASDF_INSTALL_PATH}”/bin` 目录中的二进制文件并为其创建垫片。 - 输出包含可执行文件的路径列表,路径以空格分隔。 - 路径必须相对于 `ASDF_INSTALL_PATH`。示例输出如下: ```bash bin tools veggies ``` 这将指示 asdf 为以下目录中的文件创建垫片: - `“${ASDF_INSTALL_PATH}”/bin` - `“${ASDF_INSTALL_PATH}”/tools` - `“${ASDF_INSTALL_PATH}”/veggies` **脚本可用的环境变量** - `ASDF_INSTALL_TYPE`:`version` 或 `ref` - `ASDF_INSTALL_VERSION`: - 若 `ASDF_INSTALL_TYPE=version`,则为完整版本号。 - 如果 `ASDF_INSTALL_TYPE=ref`,则为 Git 引用(标签/提交/分支)。 - `ASDF_INSTALL_PATH`:工具 _已_ 安装或 _应_ 安装的路径。 **调用此脚本的命令** - `asdf install <tool> [version]`:初始创建二进制文件的垫片。 - `asdf reshim <tool> <version>`:重新创建二进制文件的垫片。 **asdf 核心的调用签名** ```bash "${plugin_path}/bin/list-bin-paths" ``` --- ### `bin/exec-env` **描述** 在执行工具二进制文件的垫片之前,请先准备好环境。 **脚本可用的环境变量** - `ASDF_INSTALL_TYPE`: `version` 或 `ref` - `ASDF_INSTALL_VERSION`: - 如果 `ASDF_INSTALL_TYPE=version`,则为完整版本号。 - 如果 `ASDF_INSTALL_TYPE=ref`,则为 Git 引用(标签/提交/分支)。 - `ASDF_INSTALL_PATH`:工具 _已_ 安装或 _应_ 安装的路径。 **调用此脚本的命令** - `asdf which <command>`:显示可执行文件的路径 - `asdf exec <command> [args...]`:执行当前版本的命令垫片 - `asdf env <command> [util]`:在命令垫片执行所用的环境中运行工具(默认:`env`)。 **asdf 核心的调用签名** ```bash "${plugin_path}/bin/exec-env" ``` --- ### `bin/exec-path` 获取指定版本工具的可执行文件路径。必须输出一个 包含相对可执行文件路径的字符串。这允许插件 在满足条件时覆盖 Shim 指定的可执行文件路径,否则返回 Shim 指定的默认路径。 **描述** 获取指定版本工具的可执行文件路径。 **实现细节** - 必须输出包含相对可执行文件路径的字符串。 - 条件性地覆盖shim指定的可执行文件路径,否则返回shim指定的默认路径。 ```shell Usage: plugin/bin/exec-path <install-path> <command> <executable-path> Example Call: ~/.asdf/plugins/foo/bin/exec-path "~/.asdf/installs/foo/1.0" "foo" "bin/foo" Output: bin/foox ``` **脚本可用的环境变量** - `ASDF_INSTALL_TYPE`: `version` 或 `ref` - `ASDF_INSTALL_VERSION`: - 若 `ASDF_INSTALL_TYPE=version`,则为完整版本号。 - 如果 `ASDF_INSTALL_TYPE=ref`,则为 Git 引用(标签/提交/分支)。 - `ASDF_INSTALL_PATH`:工具 _已_ 安装或 _应_ 安装的路径。 **调用此脚本的命令** - `asdf which <command>`:显示可执行文件的路径 - `asdf exec <command> [args...]`:执行当前版本的命令垫片 - `asdf env <command> [util]`:在命令垫片执行所用的环境中运行工具(默认:`env`)。 **asdf 核心的调用签名** ```bash "${plugin_path}/bin/exec-path" "$install_path" "$cmd" "$relative_path" ``` --- ### `bin/uninstall` **描述** 卸载提供的工具版本。 **输出格式** 输出应根据用户情况发送到 `stdout` 或 `stderr`。后续核心执行不会读取任何输出。 **脚本可用的环境变量** 此脚本未提供任何环境变量。 **调用此脚本的命令** - `asdf list all <name> <version>` - `asdf uninstall nodejs 18.15.0`:卸载 nodejs 的版本`18.15.0`,并移除所有垫片,包括通过`npm i -g`全局安装的垫片 **asdf 核心的调用签名** 不提供参数。 ```bash "${plugin_path}/bin/uninstall" ``` --- ### `bin/list-legacy-filenames` **描述** 列出用于确定指定工具版本的旧配置文件名。 **实现细节** - 输出以空格分隔的文件名列表。 ```bash .ruby-version .rvmrc ``` - 仅适用于在 `“${HOME}”/.asdfrc` 中启用了 `legacy_version_file` 选项的用户。 **脚本可用的环境变量** - `ASDF_INSTALL_TYPE`:`version` 或 `ref` - `ASDF_INSTALL_VERSION`: - 若 `ASDF_INSTALL_TYPE=version`,则为完整版本号。 - 若 `ASDF_INSTALL_TYPE=ref`,则为 Git 引用(标签/提交/分支)。 - `ASDF_INSTALL_PATH`:工具 _已_ 安装或 _应_ 安装的路径。 **调用此脚本的命令** 任何读取工具版本的命令。 **asdf 核心的调用签名** 未提供参数。 ```bash "${plugin_path}/bin/list-legacy-filenames" ``` --- ### `bin/parse-legacy-file` **描述** 解析由 asdf 找到的旧文件以确定工具的版本。适用于从 JavaScript 的 `package.json` 或 Go 的 `go.mod` 等文件中提取版本号。 **实现细节** - 如果不存在,asdf 将简单地使用 `cat` 命令读取旧文件以确定版本。 - 该过程应具有 **确定性**,并始终返回完全相同的版本: - 当解析同一旧文件时。 - 无论机器上安装了什么,或旧版本是否有效或完整。某些旧文件格式可能不适用。 - 输出包含版本号的单行内容: ```bash 1.2.3 ``` **脚本可用的环境变量** 在调用此脚本之前,没有专门设置的环境变量。 **调用此脚本的命令** 任何读取工具版本的命令。 **asdf 核心的调用签名** 该脚本应接受一个参数,即读取其内容的旧版文件的路径。 ```bash "${plugin_path}/bin/parse-legacy-file" "$file_path" ``` --- ### `bin/post-plugin-add` **描述** 在使用 `asdf plugin add <tool>` 命令将插件添加到 asdf 之后,执行此回调脚本。 参见相关命令钩子: - `pre_asdf_plugin_add` - `pre_asdf_plugin_add_${plugin_name}` - `post_asdf_plugin_add` - `post_asdf_plugin_add_${plugin_name}` **脚本可用的环境变量** - `ASDF_PLUGIN_PATH`:插件的安装路径。 - `ASDF_PLUGIN_SOURCE_URL`:插件来源 URL。可以是本地目录路径。 **asdf 核心的调用签名** 未提供参数。 ```bash "${plugin_path}/bin/post-plugin-add" ``` --- ### `bin/post-plugin-update` **描述** 在 asdf 使用 `asdf plugin update <tool> [<git-ref>]` 命令下载 _update_ 插件**之后**,执行此回调脚本。 参见相关命令钩子: - `pre_asdf_plugin_update` - `pre_asdf_plugin_update_${plugin_name}` - `post_asdf_plugin_update` - `post_asdf_plugin_update_${plugin_name}` **脚本可用的环境变量** - `ASDF_PLUGIN_PATH`:插件的安装路径。 - `ASDF_PLUGIN_PREV_REF`:插件的上一版本 Git 引用。 - `ASDF_PLUGIN_POST_REF`:插件的更新后 Git 引用。 **asdf 核心的调用签名** 未提供参数。 ```bash "${plugin_path}/bin/post-plugin-update" ``` --- ### `bin/pre-plugin-remove` **描述** 在使用 `asdf plugin remove <工具>` 命令移除插件**之前**,执行此回调脚本。 参见相关命令钩子: - `pre_asdf_plugin_remove` - `pre_asdf_plugin_remove_${plugin_name}` - `post_asdf_plugin_remove` - `post_asdf_plugin_remove_${plugin_name}` **脚本可用的环境变量** - `ASDF_PLUGIN_PATH`:插件的安装路径。 **asdf 核心的调用签名** 未提供参数。 ```bash "${plugin_path}/bin/pre-plugin-remove" ``` <!-- TODO: document command hooks --> <!-- ## Command Hooks --> ## asdf 命令行的扩展命令 <Badge type="danger" text="进阶" vertical="middle" /> 插件可以通过提供 `lib/commands/command*.bash` 脚本或者可执行程序来定义新的 asdf 命令,这些脚本或可执行程序将使用插件名称作为 asdf 命令的子命令进行调用。 例如,假设一个 `foo` 插件有以下文件: ```shell foo/ lib/commands/ command.bash command-bat.bash command-bat-man.bash command-help.bash ``` 用户现在可以执行: ```shell $ asdf foo # 等同于运行 `$ASDF_DATA_DIR/plugins/foo/lib/commands/command.bash` $ asdf foo bar # 等同于运行 `$ASDF_DATA_DIR/plugins/foo/lib/commands/command.bash bar` $ asdf foo help # 等同于运行 `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-help.bash` $ asdf foo bat man # 等同于运行 `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-bat-man.bash` $ asdf foo bat baz # 等同于运行 `$ASDF_DATA_DIR/plugins/foo/lib/commands/command-bat.bash baz` ``` 插件作者可以使用此功能来提供与其工具相关的实用命令,或者可以创建 asdf 本身的新命令扩展的插件。 如果可执行位被设置,脚本将被执行,替换 asdf 的执行。 如果可执行位未被设置,asdf 将把脚本作为 Bash 脚本加载。 `$ASDF_CMD_FILE` 解析为正在加载的文件的完整路径。 这个功能的一个很好的例子是像 [`haxe`](https://github.com/asdf-community/asdf-haxe) 这样的插件。 它提供了 `asdf haxe neko-dylibs-link` 来修复 haxe 可执行文件期望找到相对于可执行目录的动态链接库的问题。 如果你的插件提供了 asdf 扩展命令,请务必在插件的 README 文件中提及。 ## 自定义垫片模板 <Badge type="danger" text="进阶" vertical="middle" /> ::: warning 警告 请仅在**真的需要**时才使用此功能 ::: asdf 允许自定义垫片模板。对于名为 `foo` 的可执行程序,如果有一个 `shims/foo` 的文件在插件中,那么 asdf 将复制这个文件替代使用标准垫片模板。 **这必须谨慎使用。** 据 asdf 核心团队所知,此功能仅在官方 [Elixir 插件](https://github.com/asdf-vm/asdf-elixir) 中使用。这是 因为可执行文件不仅被视为可执行文件,还被视为 Elixir 文件。这使得无法使用标准的 Bash 垫片。 ## 测试 `asdf` 包含 `plugin-test` 命令用于测试插件: ```shell asdf plugin test <plugin-name> <plugin-url> [--asdf-tool-version <version>] [--asdf-plugin-gitref <git-ref>] [test-command*] ``` - `<plugin_name>` 和 `<plugin_url>` 是必需的 - 如果指定了可选参数 `[--asdf-tool-version <version>]`,工具将以该特定版本进行安装。默认值为 `asdf latest <plugin_name>` - 如果指定了可选参数 `[--asdf-plugin-gitref <git_ref>]`,插件本身将从指定的提交/分支/标签检出。这在测试插件的 CI 中的拉取请求时非常有用。默认使用插件仓库的默认分支。 - 可选参数 `[test_command...]` 是用于验证已安装工具是否正常工作的命令。通常为 `<tool> --version` 或 `<tool> --help`。例如,要测试 NodeJS 插件,我们可以运行 ```shell # asdf plugin test <plugin_name> <plugin_url> [test_command] asdf plugin test nodejs https://github.com/asdf-vm/asdf-nodejs.git node --version ``` ::: tip 注意 我们强烈建议你在 CI 环境中测试你的插件,并确保它可以在 Linux 和 OSX 上运行。 ::: ### GitHub Action [asdf-vm/actions](https://github.com/asdf-vm/actions) 仓库提供了一个 GitHub Action,用于测试托管在 GitHub 上的插件。一个示例 `.github/workflows/test.yaml` Actions 工作流如下: ```yaml 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" ``` #### TravisCI 配置示例 这是一个 `.travis.yml` 示例文件,请根据你的需要进行自定义: ```yaml language: c 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 os: - linux - osx ``` ::: tip 注意 当使用其他 CI 时,可能需要传递插件位置的相对路径: ```shell asdf plugin test <tool_name> <path> '<tool_command> --version' ``` ::: ## API 频率限制 如果某个命令依赖于访问外部 API,例如 `bin/list-all` 或 `bin/latest-stable`,那么在自动化测试过程中可能会遇到频率限制问题。为了解决这个问题,请确保存在一条代码路径,通过环境变量提供认证令牌。例如: ```shell cmd="curl -s" if [ -n "$GITHUB_API_TOKEN" ]; then cmd="$cmd -H 'Authorization: token $GITHUB_API_TOKEN'" fi cmd="$cmd $releases_path" ``` ### `GITHUB_API_TOKEN` 要使用 `GITHUB_API_TOKEN`,请创建一个 [新个人令牌](https://github.com/settings/tokens/new),仅授予 `public_repo` 访问权限。 然后将此令牌添加到 CI 管道的环境变量中。 ::: tip 注意 **切勿**将认证令牌发布到代码仓库中 ::: ## 插件缩写索引 ::: tip 注意 插件的推荐安装方法是通过直接 URL 安装: ```shell # asdf plugin add <name> <git_url> asdf plugin add nodejs https://github.com/asdf-vm/asdf-nodejs ``` ::: 如果未提供 `git_url`,asdf 将使用 [缩写索引仓库](https://github.com/asdf-vm/asdf-plugins) 来确定要使用的确切 `git_url`。 您可以通过遵循该仓库中的 [缩写索引](https://github.com/asdf-vm/asdf-plugins) 中的说明,将你的插件添加到缩写索引中。 ================================================ FILE: go.mod ================================================ module github.com/asdf-vm/asdf go 1.24.9 require ( github.com/go-git/go-git/v5 v5.16.5 github.com/mgechev/revive v1.7.0 github.com/otiai10/copy v1.14.0 github.com/rogpeppe/go-internal v1.14.1 github.com/stretchr/testify v1.10.0 github.com/urfave/cli/v3 v3.3.3 golang.org/x/sys v0.38.0 gopkg.in/ini.v1 v1.67.0 honnef.co/go/tools v0.5.1 mvdan.cc/gofumpt v0.7.0 ) require ( dario.cat/mergo v1.0.0 // indirect github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.1.6 // indirect github.com/chavacava/garif v0.1.0 // indirect github.com/cloudflare/circl v1.6.1 // indirect github.com/cyphar/filepath-securejoin v0.4.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/fatih/color v1.18.0 // indirect github.com/fatih/structtag v1.2.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/skeema/knownhosts v1.3.1 // indirect github.com/spf13/afero v1.12.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect golang.org/x/crypto v0.45.0 // indirect golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 // indirect golang.org/x/mod v0.29.0 // indirect golang.org/x/net v0.47.0 // indirect golang.org/x/sync v0.18.0 // indirect golang.org/x/text v0.31.0 // indirect golang.org/x/tools v0.38.0 // indirect golang.org/x/tools/go/expect v0.1.1-deprecated // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) ================================================ FILE: go.sum ================================================ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw= github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/chavacava/garif v0.1.0 h1:2JHa3hbYf5D9dsgseMKAmc/MZ109otzgNFk5s87H9Pc= github.com/chavacava/garif v0.1.0/go.mod h1:XMyYCkEL58DF0oyW4qDjjnPWONs2HBqYKI+UIPD+Gww= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o= github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM= github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s= github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517 h1:zpIH83+oKzcpryru8ceC6BxnoG8TBrhgAvRg8obzup0= github.com/mgechev/dots v0.0.0-20210922191527-e955255bf517/go.mod h1:KQ7+USdGKfpPjXk4Ga+5XxQM4Lm4e3gAogrreFAYpOg= github.com/mgechev/revive v1.7.0 h1:JyeQ4yO5K8aZhIKf5rec56u0376h8AlKNQEmjfkjKlY= github.com/mgechev/revive v1.7.0/go.mod h1:qZnwcNhoguE58dfi96IJeSTPeZQejNeoMQLUZGi4SW4= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= github.com/otiai10/copy v1.14.0/go.mod h1:ECfuL02W+/FkTWZWgQqXPWZgW9oeKCSQ5qVfSc4qc4w= github.com/otiai10/mint v1.5.1 h1:XaPLeE+9vGbuyEHem1JNk3bYc7KKqyI/na0/mLd/Kks= github.com/otiai10/mint v1.5.1/go.mod h1:MJm72SBthJjz8qhefc4z1PYEieWmy8Bku7CjcAqyUSM= github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/urfave/cli/v3 v3.3.3 h1:byCBaVdIXuLPIDm5CYZRVG6NvT7tv1ECqdU4YzlEa3I= github.com/urfave/cli/v3 v3.3.3/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678 h1:1P7xPZEwZMoBoz0Yze5Nx2/4pxj6nw9ZqHWXqP0iRgQ= golang.org/x/exp/typeparams v0.0.0-20231108232855-2478ac86f678/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.5.1 h1:4bH5o3b5ZULQ4UrBmP+63W9r7qIkqJClEA9ko5YKx+I= honnef.co/go/tools v0.5.1/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= mvdan.cc/gofumpt v0.7.0 h1:bg91ttqXmi9y2xawvkuMXyvAA/1ZGJqYAEGjXuP0JXU= mvdan.cc/gofumpt v0.7.0/go.mod h1:txVFJy/Sc/mvaycET54pV8SW8gWxTlUuGHVEcncmNUo= ================================================ FILE: help.txt ================================================ MANAGE PLUGINS asdf plugin add <name> [<git-url>] Add a plugin from the plugin repo OR, add a Git repo as a plugin by specifying the name and repo url asdf plugin list [--urls] [--refs] List installed plugins. Optionally show git urls and git-ref asdf plugin list all List plugins registered on asdf-plugins repository with URLs asdf plugin remove <name> Remove plugin and package versions asdf plugin update <name> [<git-ref>] Update a plugin to latest commit on default branch or a particular git-ref asdf plugin update --all Update all plugins to latest commit on default branch MANAGE PACKAGES asdf current Display current version set or being used for all packages asdf current <name> Display current version set or being used for package asdf global <name> <version> Set the package global version asdf global <name> latest[:<version>] Set the package global version to the latest provided version asdf help <name> [<version>] Output documentation for plugin and tool asdf install Install all the package versions listed in the .tool-versions file asdf install <name> Install one tool at the version specified in the .tool-versions file asdf install <name> <version> Install a specific version of a package asdf install <name> latest[:<version>] Install the latest stable version of a package, or with optional version, install the latest stable version that begins with the given string asdf latest <name> [<version>] Show latest stable version of a package asdf latest --all Show latest stable version of all the packages and if they are installed asdf list <name> [version] List installed versions of a package and optionally filter the versions asdf list all <name> [<version>] List all versions of a package and optionally filter the returned versions asdf local <name> <version> Set the package local version asdf local <name> latest[:<version>] Set the package local version to the latest provided version asdf shell <name> <version> Set the package version to `ASDF_${LANG}_VERSION` in the current shell asdf uninstall <name> <version> Remove a specific version of a package asdf where <name> [<version>] Display install path for an installed or current version asdf which <command> Display the path to an executable UTILS 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. asdf info Print OS, Shell and ASDF debug information. asdf version Print the currently installed version of ASDF asdf reshim <name> <version> Recreate shims for version of a package asdf shim-versions <command> List the plugins and versions that provide a command asdf update Update asdf to the latest stable release asdf update --head Update asdf to the latest on the master branch RESOURCES GitHub: https://github.com/asdf-vm/asdf Docs: https://asdf-vm.com ================================================ FILE: internal/cli/cli.go ================================================ // Package cli contains the asdf CLI command code package cli import ( "context" "errors" "fmt" "io" "io/fs" "log" "net/mail" "os" "path/filepath" "slices" "strings" "text/tabwriter" "github.com/asdf-vm/asdf/internal/cli/set" "github.com/asdf-vm/asdf/internal/completions" "github.com/asdf-vm/asdf/internal/config" "github.com/asdf-vm/asdf/internal/exec" "github.com/asdf-vm/asdf/internal/execenv" "github.com/asdf-vm/asdf/internal/execute" "github.com/asdf-vm/asdf/internal/help" "github.com/asdf-vm/asdf/internal/hook" "github.com/asdf-vm/asdf/internal/info" "github.com/asdf-vm/asdf/internal/installs" "github.com/asdf-vm/asdf/internal/pluginindex" "github.com/asdf-vm/asdf/internal/plugins" "github.com/asdf-vm/asdf/internal/resolve" "github.com/asdf-vm/asdf/internal/shims" "github.com/asdf-vm/asdf/internal/toolversions" "github.com/asdf-vm/asdf/internal/versions" "github.com/urfave/cli/v3" ) const usageText = `The Multiple Runtime Version Manager. Manage all your runtime versions with one tool! Complete documentation is available at https://asdf-vm.com/` const updateCommandRemovedText = ` Upgrading asdf via asdf update is no longer supported. Please use your OS package manager (Homebrew, APT, etc...) to upgrade asdf or download the latest asdf binary manually from the asdf website. Please visit https://asdf-vm.com/ or https://github.com/asdf-vm/asdf for more details.` // Execute defines the full CLI API and then runs it func Execute(version string) { logger := log.New(os.Stderr, "", 0) log.SetFlags(0) app := &cli.Command{ Name: "asdf", Version: version, Copyright: "(c) 2024 Trevor Brown", Authors: []any{ mail.Address{Name: "Trevor Brown", Address: "someguy@example.com"}, }, Usage: "The multiple runtime version manager", UsageText: usageText, Commands: []*cli.Command{ { Name: "cmd", Action: func(_ context.Context, cmd *cli.Command) error { args := cmd.Args().Slice() return extensionCommand(logger, args) }, }, { Name: "completion", Action: func(_ context.Context, cmd *cli.Command) error { shell := cmd.Args().Get(0) return completionCommand(logger, shell) }, }, { Name: "current", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "no-header", Usage: "Whether or not to print a header line", }, }, Action: func(_ context.Context, cmd *cli.Command) error { tool := cmd.Args().Get(0) noHeader := cmd.Bool("no-header") return currentCommand(logger, tool, noHeader) }, }, { Name: "env", Action: func(_ context.Context, cmd *cli.Command) error { shimmedCommand := cmd.Args().Get(0) args := cmd.Args().Slice() return envCommand(logger, shimmedCommand, args) }, }, { Name: "exec", // We want all arguments to exec to remain unparsed so we can pass them // directly to the command asdf will exec on behalf of the shim/user. // SkipFlagParsing tells urfave/cli to do this. SkipFlagParsing: true, Action: func(_ context.Context, cmd *cli.Command) error { command := cmd.Args().Get(0) args := cmd.Args().Slice() return execCommand(logger, command, args) }, }, { Name: "help", Action: func(_ context.Context, cmd *cli.Command) error { toolName := cmd.Args().Get(0) toolVersion := cmd.Args().Get(1) return helpCommand(logger, version, toolName, toolVersion) }, }, { Name: "info", Action: func(_ context.Context, _ *cli.Command) error { conf, err := config.LoadConfig() if err != nil { logger.Printf("error loading config: %s", err) return err } return infoCommand(conf, version) }, }, { Name: "version", Action: func(_ context.Context, _ *cli.Command) error { fmt.Fprintf(os.Stdout, "%s\n", version) return nil }, }, { Name: "install", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "keep-download", Usage: "Whether or not to keep download directory after successful install", }, }, Action: func(_ context.Context, cmd *cli.Command) error { args := cmd.Args() keepDownload := cmd.Bool("keep-download") return installCommand(logger, args.Get(0), args.Get(1), keepDownload) }, }, { Name: "latest", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "all", Usage: "Show latest version of all tools", }, }, Action: func(_ context.Context, cmd *cli.Command) error { tool := cmd.Args().Get(0) pattern := cmd.Args().Get(1) all := cmd.Bool("all") return latestCommand(logger, all, tool, pattern) }, }, { Name: "list", Action: func(_ context.Context, cmd *cli.Command) error { args := cmd.Args() return listCommand(logger, args.Get(0), args.Get(1), args.Get(2)) }, }, { Name: "plugin", Commands: []*cli.Command{ { Name: "add", Action: func(_ context.Context, cmd *cli.Command) error { args := cmd.Args() conf, err := config.LoadConfig() if err != nil { logger.Printf("error loading config: %s", err) return err } return pluginAddCommand(cmd, conf, logger, args.Get(0), args.Get(1)) }, }, { Name: "list", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "urls", Usage: "Show URLs", }, &cli.BoolFlag{ Name: "refs", Usage: "Show Refs", }, }, Action: func(_ context.Context, cmd *cli.Command) error { return pluginListCommand(cmd, logger) }, Commands: []*cli.Command{ { Name: "all", Action: func(_ context.Context, _ *cli.Command) error { return pluginListAllCommand(logger) }, }, }, }, { Name: "remove", Action: func(_ context.Context, cmd *cli.Command) error { args := cmd.Args() return pluginRemoveCommand(cmd, logger, args.Get(0)) }, }, { Name: "update", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "all", Usage: "Update all installed plugins", }, }, Action: func(_ context.Context, cmd *cli.Command) error { args := cmd.Args() return pluginUpdateCommand(cmd, logger, args.Get(0), args.Get(1)) }, }, { Name: "test", Flags: []cli.Flag{ &cli.StringFlag{ Name: "asdf-tool-version", Usage: "The tool version to use during testing", }, &cli.StringFlag{ Name: "asdf-plugin-gitref", Usage: "The plugin Git ref to test", }, }, Action: func(_ context.Context, cmd *cli.Command) error { toolVersion := cmd.String("asdf-tool-version") gitRef := cmd.String("asdf-plugin-gitref") args := cmd.Args().Slice() pluginTestCommand(logger, args, toolVersion, gitRef) return nil }, }, }, }, { Name: "reshim", Action: func(_ context.Context, cmd *cli.Command) error { args := cmd.Args() return reshimCommand(logger, args.Get(0), args.Get(1)) }, }, { Name: "set", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "home", Aliases: []string{"u"}, Usage: "The version should be set in the current users home directory", }, &cli.BoolFlag{ Name: "parent", Aliases: []string{"p"}, Usage: "The version should be set in the closest existing .tool-versions file in a parent directory", }, }, Action: func(_ context.Context, cmd *cli.Command) error { args := cmd.Args().Slice() home := cmd.Bool("home") parent := cmd.Bool("parent") return set.Main(os.Stdout, os.Stderr, args, home, parent, func() (string, error) { return os.UserHomeDir() }) }, }, { Name: "shimversions", Action: func(_ context.Context, cmd *cli.Command) error { args := cmd.Args() return shimVersionsCommand(logger, args.Get(0)) }, }, { Name: "uninstall", Action: func(_ context.Context, cmd *cli.Command) error { tool := cmd.Args().Get(0) version := cmd.Args().Get(1) return uninstallCommand(logger, tool, version) }, }, { Name: "update", Action: func(_ context.Context, _ *cli.Command) error { fmt.Println(updateCommandRemovedText) return errors.New("command removed") }, }, { Name: "where", Action: func(_ context.Context, cmd *cli.Command) error { tool := cmd.Args().Get(0) version := cmd.Args().Get(1) return whereCommand(logger, tool, version) }, }, { Name: "which", Action: func(_ context.Context, cmd *cli.Command) error { tool := cmd.Args().Get(0) return whichCommand(logger, tool) }, }, }, CommandNotFound: func(_ context.Context, _ *cli.Command, s string) { logger.Printf("invalid command provided: %s\n\n", s) helpCommand(logger, version, "", "") cli.OsExiter(1) }, } err := unsetAsdfReservedEnvVars() if err != nil { cli.OsExiter(1) } if err = app.Run(context.Background(), os.Args); err != nil { cli.OsExiter(1) } } func completionCommand(l *log.Logger, shell string) error { file, ok := completions.Get(shell) if !ok { l.Printf(`No completions available for shell with name %q Completions are available for: %v`, shell, strings.Join(completions.Names(), ", ")) return errors.New("bad shell name") } defer file.Close() io.Copy(os.Stdout, file) return nil } // This function is a whole mess and needs to be refactored func currentCommand(logger *log.Logger, tool string, noHeader bool) error { conf, err := config.LoadConfig() if err != nil { logger.Printf("error loading config: %s", err) return err } currentDir, err := os.Getwd() if err != nil { logger.Printf("unable to get current directory: %s", err) return err } // settings here to match legacy implementation w := tabwriter.NewWriter(os.Stdout, 16, 0, 1, ' ', 0) if !noHeader { writeHeader(w) } if tool == "" { // show all allPlugins, err := plugins.List(conf, false, false) if err != nil { return err } if len(allPlugins) < 1 { fmt.Println("No plugins installed") return nil } for _, plugin := range allPlugins { toolversion, versionFound, versionInstalled := getVersionInfo(conf, plugin, currentDir) formatCurrentVersionLine(w, plugin, toolversion, versionFound, versionInstalled, err) } w.Flush() return nil } // show single tool plugin := plugins.New(conf, tool) err = plugin.Exists() _, ok := err.(plugins.PluginMissing) pluginExists := !ok if pluginExists { toolversion, versionFound, versionInstalled := getVersionInfo(conf, plugin, currentDir) formatCurrentVersionLine(w, plugin, toolversion, versionFound, versionInstalled, err) w.Flush() if !versionFound { os.Exit(126) } if !versionInstalled { cli.OsExiter(1) } } else { fmt.Printf("No such plugin: %s\n", tool) return err } return nil } func getVersionInfo(conf config.Config, plugin plugins.Plugin, currentDir string) (resolve.ToolVersions, bool, bool) { toolversion, found, _ := resolve.Version(conf, plugin, currentDir) installed := false if found { firstVersion := toolversion.Versions[0] version := toolversions.Parse(firstVersion) installed = installs.IsInstalled(conf, plugin, version) } return toolversion, found, installed } func writeHeader(w *tabwriter.Writer) { fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", "Name", "Version", "Source", "Installed") } func formatCurrentVersionLine(w *tabwriter.Writer, plugin plugins.Plugin, toolversion resolve.ToolVersions, found bool, installed bool, err error) error { if err != nil { return err } // columns are: name, version, source, installed version := formatVersions(toolversion.Versions) source := formatSource(toolversion, found) installedStatus := formatInstalled(toolversion, plugin.Name, found, installed) fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", plugin.Name, version, source, installedStatus) return nil } func formatInstalled(toolversion resolve.ToolVersions, name string, found, installed bool) string { if !found { return "" } if !installed { return fmt.Sprintf("false - Run `asdf install %s %s`", name, toolversion.Versions[0]) } return "true" } func formatSource(toolversion resolve.ToolVersions, found bool) string { if found { return filepath.Join(toolversion.Directory, toolversion.Source) } return "______" } func formatVersions(versions []string) string { switch len(versions) { case 0: return "______" case 1: return versions[0] default: return strings.Join(versions, " ") } } func envCommand(logger *log.Logger, shimmedCommand string, args []string) error { command := "env" if shimmedCommand == "" { logger.Printf("usage: asdf env <command>") return fmt.Errorf("usage: asdf env <command>") } if len(args) >= 2 { command = args[1] } realArgs := []string{} if len(args) > 2 { realArgs = args[2:] } conf, err := config.LoadConfig() if err != nil { logger.Printf("error loading config: %s", err) return err } _, plugin, version, err := getExecutable(logger, conf, shimmedCommand) if err != nil { return err } parsedVersion := toolversions.Parse(version) execPaths, err := shims.ExecutablePaths(conf, plugin, parsedVersion) if err != nil { return err } env := map[string]string{ "ASDF_INSTALL_TYPE": parsedVersion.Type, "ASDF_INSTALL_VERSION": parsedVersion.Value, "ASDF_INSTALL_PATH": installs.InstallPath(conf, plugin, parsedVersion), "PATH": setPath(execPaths), } if parsedVersion.Type != "system" { env, err = execenv.Generate(plugin, env) if _, ok := err.(plugins.NoCallbackError); !ok && err != nil { return err } } fname, err := shims.ExecutableOnPath(env["PATH"], command) if err != nil { return err } finalEnv := execute.MergeWithCurrentEnv(env) err = exec.Exec(fname, realArgs, finalEnv) if err != nil { fmt.Printf("err %#+v\n", err.Error()) } return err } func setPath(paths []string) string { return strings.Join(paths, ":") + ":" + os.Getenv("PATH") } func execCommand(logger *log.Logger, command string, args []string) error { if command == "" { logger.Printf("usage: asdf exec <command>") return fmt.Errorf("usage: asdf exec <command>") } conf, err := config.LoadConfig() if err != nil { logger.Printf("error loading config: %s", err) return err } executable, plugin, version, err := getExecutable(logger, conf, command) if err != nil { return err } if len(args) > 1 { args = args[1:] } else { args = []string{} } parsedVersion := toolversions.Parse(version) execPaths, err := shims.ExecutablePaths(conf, plugin, parsedVersion) if err != nil { return err } env := map[string]string{ "ASDF_INSTALL_TYPE": parsedVersion.Type, "ASDF_INSTALL_VERSION": parsedVersion.Value, "ASDF_INSTALL_PATH": installs.InstallPath(conf, plugin, parsedVersion), "PATH": setPath(execPaths), } if parsedVersion.Type != "system" { env, err = execenv.Generate(plugin, env) if _, ok := err.(plugins.NoCallbackError); !ok && err != nil { return err } } err = hook.RunWithOutput(conf, fmt.Sprintf("pre_%s_%s", plugin.Name, filepath.Base(executable)), args, os.Stdout, os.Stderr) if err != nil { cli.OsExiter(1) return err } finalEnv := execute.MergeWithCurrentEnv(env) return exec.Exec(executable, args, finalEnv) } func extensionCommand(logger *log.Logger, args []string) error { if len(args) < 1 { err := errors.New("no plugin name specified") logger.Printf("%s", err.Error()) return err } conf, err := config.LoadConfig() if err != nil { logger.Printf("error loading config: %s", err) return err } pluginName := args[0] plugin := plugins.New(conf, pluginName) err = runExtensionCommand(plugin, args[1:]) logger.Printf("error running extension command: %s", err.Error()) return err } func runExtensionCommand(plugin plugins.Plugin, args []string) (err error) { path := "" if len(args) > 0 { path, err = plugin.ExtensionCommandPath(args[0]) if err != nil { path, err = plugin.ExtensionCommandPath("") if err != nil { return err } } else { args = args[1:] } } else { path, err = plugin.ExtensionCommandPath("") if err != nil { return err } } return exec.Exec(path, args, os.Environ()) } func getExecutable(logger *log.Logger, conf config.Config, command string) (executable string, plugin plugins.Plugin, version string, err error) { currentDir, err := os.Getwd() if err != nil { logger.Printf("unable to get current directory: %s", err) return "", plugins.Plugin{}, "", err } executable, plugin, version, found, err := shims.FindExecutable(conf, command, currentDir) if err != nil { if _, ok := err.(shims.NoExecutableForPluginError); ok { logger.Printf("No executable %s found for current version. Please select a different version or install %s manually for the current version", command, command) cli.OsExiter(1) return "", plugin, version, err } shimPath := shims.Path(conf, command) toolVersions, _ := shims.GetToolsAndVersionsFromShimFile(shimPath) if len(toolVersions) > 0 { if anyInstalled(conf, toolVersions) { logger.Printf("No version is set for command %s", command) logger.Printf("Consider adding one of the following versions in your config file at %s/.tool-versions\n", currentDir) } else { logger.Printf("No preset version installed for command %s", command) for _, toolVersion := range toolVersions { for _, version := range toolVersion.Versions { logger.Printf("asdf install %s %s\n", toolVersion.Name, version) } } logger.Printf("or add one of the following versions in your config file at %s/.tool-versions\n", currentDir) } for _, toolVersion := range toolVersions { for _, version := range toolVersion.Versions { logger.Printf("%s %s\n", toolVersion.Name, version) } } } os.Exit(126) return executable, plugins.Plugin{}, "", err } if !found { logger.Print("executable not found") os.Exit(126) return executable, plugins.Plugin{}, "", fmt.Errorf("executable not found") } return executable, plugin, version, nil } func anyInstalled(conf config.Config, toolVersions []toolversions.ToolVersions) bool { for _, toolVersion := range toolVersions { for _, version := range toolVersion.Versions { version := toolversions.Parse(version) plugin := plugins.New(conf, toolVersion.Name) if installs.IsInstalled(conf, plugin, version) { return true } } } return false } func pluginAddCommand(_ *cli.Command, conf config.Config, logger *log.Logger, pluginName, pluginRepo string) error { if pluginName == "" { // Invalid arguments // Maybe one day switch this to show the generated help // cli.ShowSubcommandHelp(cCtx) return cli.Exit("usage: asdf plugin add <name> [<git-url>]", 1) } err := plugins.Add(conf, pluginName, pluginRepo, "") if err != nil { logger.Printf("%s", err) var existsErr plugins.PluginAlreadyExists if errors.As(err, &existsErr) { os.Exit(0) return nil } cli.OsExiter(1) return nil } os.Exit(0) return nil } func pluginRemoveCommand(_ *cli.Command, logger *log.Logger, pluginName string) error { if pluginName == "" { logger.Print("No plugin given") cli.OsExiter(1) return nil } conf, err := config.LoadConfig() if err != nil { logger.Printf("error loading config: %s", err) return err } err = plugins.Remove(conf, pluginName, os.Stdout, os.Stderr) if err != nil { // Needed to match output of old version logger.Printf("%s", err) } // This feels a little hacky but it works, to re-generate shims we delete them // all and generate them again. err2 := shims.RemoveAll(conf) if err2 != nil { logger.Printf("%s", err2) cli.OsExiter(1) return err2 } shims.GenerateAll(conf, os.Stdout, os.Stderr) return err } func pluginListCommand(cCtx *cli.Command, logger *log.Logger) error { urls := cCtx.Bool("urls") refs := cCtx.Bool("refs") conf, err := config.LoadConfig() if err != nil { logger.Printf("error loading config: %s", err) return err } plugins, err := plugins.List(conf, urls, refs) if err != nil { logger.Printf("error loading plugin list: %s", err) return err } if len(plugins) == 0 { logger.Println("No plugins installed") return nil } // TODO: Add some sort of presenter logic in another file so we // don't clutter up this cmd code with conditional presentation // logic for _, plugin := range plugins { if urls && refs { fmt.Printf("%s\t\t%s\t%s\n", plugin.Name, plugin.URL, plugin.Ref) } else if refs { fmt.Printf("%s\t\t%s\n", plugin.Name, plugin.Ref) } else if urls { fmt.Printf("%s\t\t%s\n", plugin.Name, plugin.URL) } else { fmt.Printf("%s\n", plugin.Name) } } return nil } func pluginListAllCommand(logger *log.Logger) error { conf, err := config.LoadConfig() if err != nil { logger.Printf("error loading config: %s", err) return err } disableRepo, err := conf.DisablePluginShortNameRepository() if err != nil { logger.Printf("unable to check config") return err } if disableRepo { logger.Printf("Short-name plugin repository is disabled") cli.OsExiter(1) return nil } lastCheckDuration := 0 // We don't care about errors here as we can use the default value checkDuration, _ := conf.PluginRepositoryLastCheckDuration() if !checkDuration.Never { lastCheckDuration = checkDuration.Every } index := pluginindex.Build(conf.DataDir, conf.PluginIndexURL, false, lastCheckDuration) availablePlugins, err := index.Get() if err != nil { logger.Printf("error loading plugin index: %s", err) return err } installedPlugins, err := plugins.List(conf, true, false) if err != nil { logger.Printf("error loading plugin list: %s", err) return err } w := tabwriter.NewWriter(os.Stdout, 15, 0, 1, ' ', 0) for _, availablePlugin := range availablePlugins { if pluginInstalled(availablePlugin, installedPlugins) { fmt.Fprintf(w, "%s\t\t*%s\n", availablePlugin.Name, availablePlugin.URL) } else { fmt.Fprintf(w, "%s\t\t%s\n", availablePlugin.Name, availablePlugin.URL) } } w.Flush() return nil } func pluginInstalled(plugin pluginindex.Plugin, installedPlugins []plugins.Plugin) bool { for _, installedPlugin := range installedPlugins { if installedPlugin.Name == plugin.Name && installedPlugin.URL == plugin.URL { return true } } return false } func infoCommand(conf config.Config, version string) error { return info.Print(conf, version) } func helpCommand(logger *log.Logger, asdfVersion, tool, version string) error { conf, err := config.LoadConfig() if err != nil { logger.Printf("error loading config: %s", err) return err } if tool != "" { if version != "" { err := help.PrintToolVersion(conf, tool, version) if err != nil { cli.OsExiter(1) } return err } err := help.PrintTool(conf, tool) if err != nil { cli.OsExiter(1) } return err } allPlugins, err := plugins.List(conf, false, false) if err != nil { cli.OsExiter(1) } err = help.Print(asdfVersion, allPlugins) if err != nil { cli.OsExiter(1) } return err } func pluginUpdateCommand(cCtx *cli.Command, logger *log.Logger, pluginName, ref string) error { updateAll := cCtx.Bool("all") if !updateAll && pluginName == "" { return cli.Exit("usage: asdf plugin update {<name> [git-ref] | --all}", 1) } conf, err := config.LoadConfig() if err != nil { logger.Printf("error loading config: %s", err) return err } if updateAll { installedPlugins, err := plugins.List(conf, false, false) if err != nil { logger.Printf("failed to get plugin list: %s", err) return err } for _, plugin := range installedPlugins { updatedToRef, err := plugin.Update(conf, "", os.Stdout, os.Stderr) formatUpdateResult(logger, plugin.Name, updatedToRef, err) } return nil } plugin := plugins.New(conf, pluginName) updatedToRef, err := plugin.Update(conf, ref, os.Stdout, os.Stderr) formatUpdateResult(logger, pluginName, updatedToRef, err) return err } func pluginTestCommand(l *log.Logger, args []string, toolVersion, ref string) { conf, err := config.LoadConfig() if err != nil { l.Printf("error loading config: %s", err) cli.OsExiter(1) return } if len(args) < 2 { failTest(l, "please provide a plugin name and url") } name := args[0] url := args[1] testName := fmt.Sprintf("asdf-test-%s", name) // Install plugin err = plugins.Add(conf, testName, url, ref) if err != nil { failTest(l, fmt.Sprintf("%s was not properly installed reason: %s", name, err)) } // Remove plugin var blackhole strings.Builder defer plugins.Remove(conf, testName, &blackhole, &blackhole) // Assert callbacks are present plugin := plugins.New(conf, testName) files, err := os.ReadDir(filepath.Join(plugin.Dir, "bin")) if _, ok := err.(*fs.PathError); ok { failTest(l, "bin/ directory does not exist") } callbacks := []string{} for _, file := range files { callbacks = append(callbacks, file.Name()) } for _, expectedCallback := range []string{"download", "install", "list-all"} { if !slices.Contains(callbacks, expectedCallback) { failTest(l, fmt.Sprintf("missing callback %s", expectedCallback)) } } allCallbacks := []string{"download", "install", "list-all", "latest-stable", "help.overview", "help.deps", "help.config", "help.links", "list-bin-paths", "exec-env", "exec-path", "uninstall", "list-legacy-filenames", "parse-legacy-file", "post-plugin-add", "post-plugin-update", "pre-plugin-remove"} // Assert all callbacks present are executable for _, file := range files { // file is a callback... if slices.Contains(allCallbacks, file.Name()) { // check if it is executable info, _ := file.Info() if !(info.Mode()&0o111 != 0) { failTest(l, fmt.Sprintf("callback lacks executable permission: %s", file.Name())) } } } // Assert has license licensePath := filepath.Join(plugin.Dir, "LICENSE") if _, err := os.Stat(licensePath); errors.Is(err, os.ErrNotExist) { failTest(l, "LICENSE file must be present in the plugin repository") } bytes, err := os.ReadFile(licensePath) if err != nil { failTest(l, "LICENSE file must be present in the plugin repository") } // Validate license file not empty if len(bytes) == 0 { failTest(l, "LICENSE file in the plugin repository must not be empty") } // Validate it returns at least one available version var output strings.Builder err = plugin.RunCallback("list-all", []string{}, map[string]string{}, &output, &blackhole) if err != nil { failTest(l, "Unable to list available versions") } allVersions := strings.Fields(output.String()) if len(allVersions) < 1 { failTest(l, "list-all did not return any version") } // grab first version returned by list-all callback if no version provided as // a CLI argument if toolVersion == "" { toolVersion = allVersions[0] } err = versions.InstallOneVersion(conf, plugin, toolVersion, false, os.Stdout, os.Stderr) if err != nil { failTest(l, "install exited with an error") } } func failTest(logger *log.Logger, msg string) { logger.Printf("FAILED: %s", msg) cli.OsExiter(1) } func formatUpdateResult(logger *log.Logger, pluginName, updatedToRef string, err error) { if err != nil { logger.Printf("failed to update %s due to error: %s\n", pluginName, err) return } logger.Printf("updated %s to ref %s\n", pluginName, updatedToRef) } func installCommand(logger *log.Logger, toolName, version string, keepDownload bool) error { conf, err := config.LoadConfig() if err != nil { logger.Printf("error loading config: %s", err) return err } dir, err := os.Getwd() if err != nil { return fmt.Errorf("unable to fetch current directory: %w", err) } if toolName == "" { // Install all versions errs := versions.InstallAll(conf, dir, os.Stdout, os.Stderr) if len(errs) > 0 { for _, err := range errs { // Don't print error if no version set, this just means the current // dir doesn't use a particular plugin that is installed. if _, ok := err.(versions.NoVersionSetError); ok { continue } if _, ok := err.(versions.UninstallableVersionError); ok { msg := fmt.Sprintf("skipping %s\n", err.Error()) os.Stderr.Write([]byte(msg)) continue } os.Stderr.Write([]byte(err.Error())) os.Stderr.Write([]byte("\n")) } filtered := filterInstallErrors(errs) if len(filtered) > 0 { return filtered[0] } return nil } } else { // Install specific version plugin := plugins.New(conf, toolName) if version == "" { err = versions.Install(conf, plugin, dir, os.Stdout, os.Stderr) if err != nil { var vaiErr versions.VersionAlreadyInstalledError if errors.As(err, &vaiErr) { logger.Println(err) return nil } if _, ok := err.(versions.NoVersionSetError); ok { logger.Printf("No versions specified for %s in config files or environment", toolName) cli.OsExiter(1) } logger.Printf("error installing version: %v", err) return err } } else { parsedVersion := toolversions.ParseFromCliArg(version) if parsedVersion.Type == "latest" { err = versions.InstallVersion(conf, plugin, parsedVersion, os.Stdout, os.Stderr) } else { // Adding this here to get tests passing. The other versions.Install* // calls here could have a keepDownload argument added as well. PR // welcome! err = versions.InstallOneVersion(conf, plugin, version, keepDownload, os.Stdout, os.Stderr) } if err != nil { var vaiErr versions.VersionAlreadyInstalledError if errors.As(err, &vaiErr) { logger.Println(err) return nil } logger.Printf("error installing version: %v", err) } } } return err } func filterInstallErrors(errs []error) []error { var filtered []error for _, err := range errs { var vaiErr versions.VersionAlreadyInstalledError if errors.As(err, &vaiErr) { continue } if _, ok := err.(versions.NoVersionSetError); !ok { filtered = append(filtered, err) } } return filtered } func latestCommand(logger *log.Logger, all bool, toolName, pattern string) (err error) { conf, err := config.LoadConfig() if err != nil { logger.Printf("error loading config: %s", err) return err } if !all { err = latestForPlugin(conf, toolName, pattern, false) if err != nil { cli.OsExiter(1) } return err } plugins, err := plugins.List(conf, false, false) if err != nil { logger.Printf("error loading plugin list: %s", err) return err } var maybeErr error // loop over all plugins and show latest for each one. for _, plugin := range plugins { maybeErr = latestForPlugin(conf, plugin.Name, "", true) if maybeErr != nil { err = maybeErr } } if err != nil { cli.OsExiter(1) return maybeErr } return nil } func listCommand(logger *log.Logger, first, second, third string) (err error) { conf, err := config.LoadConfig() if err != nil { logger.Printf("error loading config: %s", err) return err } // Both listAllCommand and listLocalCommand need to be refactored and extracted // out into another package. if first == "all" { return listAllCommand(logger, conf, second, third) } return listLocalCommand(logger, conf, first, second) } func listAllCommand(logger *log.Logger, conf config.Config, toolName, filter string) error { if toolName == "" { logger.Print("No plugin given") cli.OsExiter(1) return nil } plugin, err := loadPlugin(logger, conf, toolName) if err != nil { cli.OsExiter(1) return err } var stdout strings.Builder var stderr strings.Builder err = plugin.RunCallback("list-all", []string{}, map[string]string{}, &stdout, &stderr) if err != nil { fmt.Printf("Plugin %s's list-all callback script failed with output:\n", plugin.Name) // Print to stderr os.Stderr.WriteString(stderr.String()) os.Stderr.WriteString(stdout.String()) cli.OsExiter(1) return err } versions := strings.Split(stdout.String(), " ") if filter != "" { versions = filterByExactMatch(versions, filter) } if len(versions) == 0 { logger.Printf("No compatible versions available (%s %s)", plugin.Name, filter) cli.OsExiter(1) return nil } for _, version := range versions { fmt.Printf("%s\n", version) } return nil } func filterByExactMatch(allVersions []string, pattern string) (versions []string) { for _, version := range allVersions { if strings.HasPrefix(version, pattern) { versions = append(versions, version) } } return versions } func listLocalCommand(logger *log.Logger, conf config.Config, pluginName, filter string) error { currentDir, err := os.Getwd() if err != nil { logger.Printf("unable to get current directory: %s", err) return err } if pluginName != "" { plugin, err := loadPlugin(logger, conf, pluginName) if err != nil { cli.OsExiter(1) return err } versions, _ := installs.Installed(conf, plugin) if filter != "" { versions = filterByExactMatch(versions, filter) } if len(versions) == 0 { if filter == "" { logger.Printf("No compatible versions installed (%s)", plugin.Name) } else { logger.Printf("No compatible versions installed (%s %s)", plugin.Name, filter) } return nil } currentVersions, _, err := resolve.Version(conf, plugin, currentDir) if err != nil { cli.OsExiter(1) return err } for _, version := range versions { if slices.Contains(currentVersions.Versions, version) { fmt.Printf(" *%s\n", version) } else { fmt.Printf(" %s\n", version) } } return nil } allPlugins, err := plugins.List(conf, false, false) if err != nil { logger.Printf("unable to list plugins due to error: %s", err) return err } for _, plugin := range allPlugins { fmt.Printf("%s\n", plugin.Name) versions, _ := installs.Installed(conf, plugin) if len(versions) > 0 { currentVersions, _, err := resolve.Version(conf, plugin, currentDir) if err != nil { cli.OsExiter(1) return err } for _, version := range versions { if slices.Contains(currentVersions.Versions, version) { fmt.Printf(" *%s\n", version) } else { fmt.Printf(" %s\n", version) } } } else { fmt.Print(" No versions installed\n") } } return nil } func reshimCommand(logger *log.Logger, tool, version string) (err error) { conf, err := config.LoadConfig() if err != nil { logger.Printf("error loading config: %s", err) return err } var plugin plugins.Plugin if tool != "" { plugin = plugins.New(conf, tool) if err := plugin.Exists(); err != nil { logger.Printf("No such plugin: %s", plugin.Name) cli.OsExiter(1) return err } } // if either tool or version are missing just regenerate all shims. This is // fast enough now. if tool == "" || version == "" { err = shims.RemoveAll(conf) if err != nil { return err } return shims.GenerateAll(conf, os.Stdout, os.Stderr) } // If provided a specific version it could be something special like a path // version so we need to generate it manually return reshimToolVersion(conf, plugin, version, os.Stdout, os.Stderr) } func shimVersionsCommand(logger *log.Logger, shimName string) error { if shimName == "" { logger.Printf("usage: asdf shimversions <command>") return fmt.Errorf("usage: asdf shimversions <command>") } conf, err := config.LoadConfig() if err != nil { logger.Printf("error loading config: %s", err) return err } shimPath := shims.Path(conf, shimName) toolVersions, err := shims.GetToolsAndVersionsFromShimFile(shimPath) for _, toolVersion := range toolVersions { for _, version := range toolVersion.Versions { fmt.Printf("%s %s\n", toolVersion.Name, version) } } return err } // This function is a whole mess and needs to be refactored func whichCommand(logger *log.Logger, command string) error { conf, err := config.LoadConfig() if err != nil { logger.Printf("error loading config: %s", err) return err } currentDir, err := os.Getwd() if err != nil { logger.Printf("unable to get current directory: %s", err) return err } if command == "" { fmt.Println("usage: asdf which <command>") return errors.New("must provide command") } path, _, _, _, err := shims.FindExecutable(conf, command, currentDir) if _, ok := err.(shims.UnknownCommandError); ok { logger.Printf("unknown command: %s. Perhaps you have to reshim?", command) return errors.New("command not found") } if _, ok := err.(shims.NoExecutableForPluginError); ok { logger.Printf("%s", err.Error()) return errors.New("no executable for tool version") } if err != nil { fmt.Printf("unexpected error: %s\n", err.Error()) return err } fmt.Printf("%s\n", path) return nil } func uninstallCommand(logger *log.Logger, tool, version string) error { if tool == "" || version == "" { logger.Print("No plugin given") cli.OsExiter(1) return nil } conf, err := config.LoadConfig() if err != nil { logger.Printf("error loading config: %s", err) cli.OsExiter(1) return err } plugin := plugins.New(conf, tool) err = versions.Uninstall(conf, plugin, version, os.Stdout, os.Stderr) if err != nil { logger.Printf("%s", err) cli.OsExiter(1) return err } // This feels a little hacky but it works, to re-generate shims we delete them // all and generate them again. err = shims.RemoveAll(conf) if err != nil { logger.Printf("%s", err) cli.OsExiter(1) return err } return shims.GenerateAll(conf, os.Stdout, os.Stderr) } func whereCommand(logger *log.Logger, tool, versionStr string) error { conf, err := config.LoadConfig() if err != nil { logger.Printf("error loading config: %s", err) return err } currentDir, err := os.Getwd() if err != nil { logger.Printf("unable to get current directory: %s", err) return err } plugin := plugins.New(conf, tool) err = plugin.Exists() if err != nil { if _, ok := err.(plugins.PluginMissing); ok { logger.Printf("No such plugin: %s", tool) } return err } version := toolversions.Parse(versionStr) if version.Type == "system" { logger.Printf("System version is selected") return errors.New("System version is selected") } if version.Value == "" { // resolve version versions, found, err := resolve.Version(conf, plugin, currentDir) if err != nil { fmt.Printf("err %#+v\n", err) return err } if found && len(versions.Versions) > 0 { versionStruct := toolversions.Version{Type: "version", Value: versions.Versions[0]} if installs.IsInstalled(conf, plugin, versionStruct) { installPath := installs.InstallPath(conf, plugin, versionStruct) fmt.Printf("%s", installPath) return nil } } // not found msg := fmt.Sprintf("No version is set for %s; please run `asdf set [options] %s <version>`", tool, tool) logger.Print(msg) return errors.New(msg) } if !installs.IsInstalled(conf, plugin, version) { logger.Printf("Version not installed") return errors.New("Version not installed") } installPath := installs.InstallPath(conf, plugin, version) fmt.Printf("%s", installPath) return nil } func loadPlugin(logger *log.Logger, conf config.Config, pluginName string) (plugins.Plugin, error) { plugin := plugins.New(conf, pluginName) err := plugin.Exists() if err != nil { logger.Printf("No such plugin: %s", pluginName) return plugin, err } return plugin, err } func reshimToolVersion(conf config.Config, plugin plugins.Plugin, versionStr string, out io.Writer, errOut io.Writer) error { version := toolversions.Parse(versionStr) return shims.GenerateForVersion(conf, plugin, version, out, errOut) } func latestForPlugin(conf config.Config, toolName, pattern string, showStatus bool) error { // show single plugin plugin := plugins.New(conf, toolName) latest, err := versions.Latest(plugin, pattern) if err != nil && err.Error() != "no latest version found" { fmt.Printf("unable to load latest version: %s\n", err) return err } if latest == "" { err := fmt.Errorf("No compatible versions available (%s %s)", toolName, pattern) fmt.Println(err.Error()) return err } if showStatus { installed := installs.IsInstalled(conf, plugin, toolversions.Version{Type: "version", Value: latest}) fmt.Printf("%s\t%s\t%s\n", plugin.Name, latest, installedStatus(installed)) } else { fmt.Printf("%s\n", latest) } return nil } func installedStatus(installed bool) string { if installed { return "installed" } return "missing" } func unsetAsdfReservedEnvVars() error { // These are environment variables which are passed via env or exec. // We strip these out to avoid any potential issues with recursive calls to asdf. asdfManagedVars := []string{"ASDF_INSTALL_TYPE", "ASDF_INSTALL_VERSION", "ASDF_INSTALL_PATH"} for _, v := range asdfManagedVars { err := os.Unsetenv(v) if err != nil { return err } } return nil } ================================================ FILE: internal/cli/set/set.go ================================================ // Package set provides the 'asdf set' command package set import ( "errors" "fmt" "io" "os" "path/filepath" "strings" "github.com/asdf-vm/asdf/internal/config" "github.com/asdf-vm/asdf/internal/plugins" "github.com/asdf-vm/asdf/internal/toolversions" "github.com/asdf-vm/asdf/internal/versions" ) // Main function is the entrypoint for the 'asdf set' command func Main(_ io.Writer, stderr io.Writer, args []string, home bool, parent bool, homeFunc func() (string, error)) error { if len(args) < 1 { return printError(stderr, "tool and version must be provided as arguments") } if len(args) < 2 { return printError(stderr, "version must be provided as an argument") } if home && parent { return printError(stderr, "home and parent flags cannot both be specified; must be one location or the other") } conf, err := config.LoadConfig() if err != nil { return printError(stderr, fmt.Sprintf("error loading config: %s", err)) } resolvedVersions := []string{} for _, version := range args[1:] { parsedVersion := toolversions.ParseFromCliArg(version) if parsedVersion.Type == "latest" { plugin := plugins.New(conf, args[0]) resolvedVersion, err := versions.Latest(plugin, parsedVersion.Value) if err != nil { return fmt.Errorf("unable to resolve latest version for %s", plugin.Name) } resolvedVersions = append(resolvedVersions, resolvedVersion) continue } resolvedVersions = append(resolvedVersions, version) } tv := toolversions.ToolVersions{Name: args[0], Versions: resolvedVersions} if home { homeDir, err := homeFunc() if err != nil { return err } filepath := filepath.Join(homeDir, conf.DefaultToolVersionsFilename) err = toolversions.WriteToolVersionsToFile(filepath, []toolversions.ToolVersions{tv}) if err != nil { err = printError(stderr, fmt.Sprintf("error writing version file: %s", err)) } return err } currentDir, err := os.Getwd() if err != nil { return printError(stderr, fmt.Sprintf("unable to get current directory: %s", err)) } if parent { // locate file in parent dir and update it path, found := findVersionFileInParentDir(conf, currentDir) if !found { return printError(stderr, fmt.Sprintf("No %s version file found in parent directory", conf.DefaultToolVersionsFilename)) } err = toolversions.WriteToolVersionsToFile(path, []toolversions.ToolVersions{tv}) if err != nil { err = printError(stderr, fmt.Sprintf("error writing version file: %s", err)) } return err } // Write new file in current dir filepath := filepath.Join(currentDir, conf.DefaultToolVersionsFilename) return toolversions.WriteToolVersionsToFile(filepath, []toolversions.ToolVersions{tv}) } func printError(stderr io.Writer, msg string) error { if !strings.HasSuffix(msg, "\n") { msg += "\n" } fmt.Fprint(stderr, msg) return errors.New(strings.TrimSuffix(msg, "\n")) } func findVersionFileInParentDir(conf config.Config, directory string) (string, bool) { directory = filepath.Dir(directory) for { path := filepath.Join(directory, conf.DefaultToolVersionsFilename) if _, err := os.Stat(path); err == nil { return path, true } if directory == "/" { return "", false } directory = filepath.Dir(directory) } } ================================================ FILE: internal/cli/set/set_test.go ================================================ package set import ( "os" "path/filepath" "strings" "testing" "github.com/stretchr/testify/assert" ) func TestAll(t *testing.T) { homeFunc := func() (string, error) { return "", nil } t.Run("prints error when no arguments specified", func(t *testing.T) { stdout, stderr := buildOutputs() err := Main(&stdout, &stderr, []string{}, false, false, homeFunc) assert.Error(t, err, "tool and version must be provided as arguments") assert.Equal(t, stdout.String(), "") assert.Equal(t, stderr.String(), "tool and version must be provided as arguments\n") }) t.Run("prints error when no version specified", func(t *testing.T) { stdout, stderr := buildOutputs() err := Main(&stdout, &stderr, []string{"lua"}, false, false, homeFunc) assert.Error(t, err, "version must be provided as an argument") assert.Equal(t, stdout.String(), "") assert.Equal(t, stderr.String(), "version must be provided as an argument\n") }) t.Run("prints error when both --parent and --home flags are set", func(t *testing.T) { stdout, stderr := buildOutputs() err := Main(&stdout, &stderr, []string{"lua", "5.2.3"}, true, true, homeFunc) assert.Error(t, err, "home and parent flags cannot both be specified; must be one location or the other") assert.Equal(t, stdout.String(), "") assert.Equal(t, stderr.String(), "home and parent flags cannot both be specified; must be one location or the other\n") }) t.Run("sets version in current directory when no flags provided", func(t *testing.T) { stdout, stderr := buildOutputs() dir := t.TempDir() assert.Nil(t, os.Chdir(dir)) err := Main(&stdout, &stderr, []string{"lua", "5.2.3"}, false, false, homeFunc) assert.Nil(t, err) assert.Equal(t, stdout.String(), "") assert.Equal(t, stderr.String(), "") path := filepath.Join(dir, ".tool-versions") bytes, err := os.ReadFile(path) assert.Nil(t, err) assert.Equal(t, "lua 5.2.3\n", string(bytes)) }) t.Run("sets version in parent directory when --parent flag provided", func(t *testing.T) { stdout, stderr := buildOutputs() dir := t.TempDir() subdir := filepath.Join(dir, "subdir") assert.Nil(t, os.Mkdir(subdir, 0o777)) assert.Nil(t, os.Chdir(subdir)) assert.Nil(t, os.WriteFile(filepath.Join(dir, ".tool-versions"), []byte("lua 4.0.0"), 0o666)) err := Main(&stdout, &stderr, []string{"lua", "5.2.3"}, false, true, homeFunc) assert.Nil(t, err) assert.Equal(t, stdout.String(), "") assert.Equal(t, stderr.String(), "") path := filepath.Join(dir, ".tool-versions") bytes, err := os.ReadFile(path) assert.Nil(t, err) assert.Equal(t, "lua 5.2.3\n", string(bytes)) }) t.Run("sets version in home directory when --home flag provided", func(t *testing.T) { stdout, stderr := buildOutputs() homedir := filepath.Join(t.TempDir(), "home") assert.Nil(t, os.Mkdir(homedir, 0o777)) err := Main(&stdout, &stderr, []string{"lua", "5.2.3"}, true, false, func() (string, error) { return homedir, nil }) assert.Nil(t, err) assert.Equal(t, stdout.String(), "") assert.Equal(t, stderr.String(), "") path := filepath.Join(homedir, ".tool-versions") bytes, err := os.ReadFile(path) assert.Nil(t, err) assert.Equal(t, "lua 5.2.3\n", string(bytes)) }) t.Run("sets version in current directory only once", func(t *testing.T) { stdout, stderr := buildOutputs() dir := t.TempDir() assert.Nil(t, os.Chdir(dir)) _ = Main(&stdout, &stderr, []string{"lua", "5.2.3"}, false, false, homeFunc) err := Main(&stdout, &stderr, []string{"lua", "5.2.3"}, false, false, homeFunc) assert.Nil(t, err) assert.Equal(t, stdout.String(), "") assert.Equal(t, stderr.String(), "") path := filepath.Join(dir, ".tool-versions") bytes, err := os.ReadFile(path) assert.Nil(t, err) assert.Equal(t, "lua 5.2.3\n", string(bytes)) }) } func buildOutputs() (strings.Builder, strings.Builder) { var stdout strings.Builder var stderr strings.Builder return stdout, stderr } ================================================ FILE: internal/completions/asdf.bash ================================================ _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]} local cmd cmd=${COMP_WORDS[1]} cmd2=${COMP_WORDS[2]} cmd3=${COMP_WORDS[3]} local prev prev=${COMP_WORDS[COMP_CWORD - 1]} local plugins plugins=$(asdf plugin list 2>/dev/null | tr '\n' ' ') # We can safely ignore warning SC2207 since it warns that it will uses the # shell's sloppy word splitting and globbing. The possible commands here are # all single words, and most likely won't contain special chars the shell will # expand. COMPREPLY=() case "$cmd" in plugin) case "$cmd2" in update) # shellcheck disable=SC2207 COMPREPLY=($(compgen -W "$plugins --all" -- "$cur")) ;; remove) # shellcheck disable=SC2207 COMPREPLY=($(compgen -W "$plugins" -- "$cur")) ;; add) local available_plugins available_plugins=$(asdf plugin list all 2>/dev/null | awk '{ if ($2 !~ /^\*/) print $1}') # shellcheck disable=SC2207 COMPREPLY=($(compgen -W "$available_plugins" -- "$cur")) ;; list) case "$cmd3" in all) ;; *) local cmds='all --urls --refs' # shellcheck disable=SC2207 COMPREPLY=($(compgen -W "$cmds" -- "$cur")) ;; esac ;; *) local cmds='add list remove update' # shellcheck disable=SC2207 COMPREPLY=($(compgen -W "$cmds" -- "$cur")) ;; esac ;; list) if [[ " $plugins " == *" $prev "* ]]; then local versions versions=$(asdf list all "$prev" 2>/dev/null) # shellcheck disable=SC2207 COMPREPLY=($(compgen -W "$versions" -- "$cur")) else case "$cmd2" in all) # shellcheck disable=SC2207 COMPREPLY=($(compgen -W "$plugins" -- "$cur")) ;; *) # shellcheck disable=SC2207 COMPREPLY=($(compgen -W "$plugins all" -- "$cur")) ;; esac fi ;; install | help) if [[ " $plugins " == *" $prev "* ]]; then local versions versions=$(asdf list all "$prev" 2>/dev/null) # shellcheck disable=SC2207 COMPREPLY=($(compgen -W "$versions" -- "$cur")) else # shellcheck disable=SC2207 COMPREPLY=($(compgen -W "$plugins" -- "$cur")) fi ;; uninstall | where | reshim) if [[ " $plugins " == *" $prev "* ]]; then local versions # The first two columns are either blank or contain the "current" marker. versions=$(asdf list "$prev" 2>/dev/null | colrm 1 2) # shellcheck disable=SC2207 COMPREPLY=($(compgen -W "$versions" -- "$cur")) else # shellcheck disable=SC2207 COMPREPLY=($(compgen -W "$plugins" -- "$cur")) fi ;; set) if [[ " $plugins " == *" $prev "* ]]; then local versions # The first two columns are either blank or contain the "current" marker. versions=$(asdf list "$prev" 2>/dev/null | colrm 1 2) versions+=" system" # shellcheck disable=SC2207 COMPREPLY=($(compgen -W "$versions" -- "$cur")) else # shellcheck disable=SC2207 COMPREPLY=($(compgen -W "$plugins -u -p" -- "$cur")) fi ;; latest) if [[ " $plugins " == *" $prev "* ]]; then local versions versions=$(asdf list all "$prev" 2>/dev/null) # shellcheck disable=SC2207 COMPREPLY=($(compgen -W "$versions" -- "$cur")) else # shellcheck disable=SC2207 COMPREPLY=($(compgen -W "$plugins --all" -- "$cur")) fi ;; exec | env | which | shimversions) # shellcheck disable=SC2207 COMPREPLY=($(compgen -W "$(_asdf_list_shims)" -- "$cur")) ;; current) # shellcheck disable=SC2207 COMPREPLY=($(compgen -W "$plugins" -- "$cur")) ;; info | version) ;; *) local cmds='current set help install latest list plugin reshim shimversions uninstall where which exec env info version' # shellcheck disable=SC2207 COMPREPLY=($(compgen -W "$cmds" -- "$cur")) ;; esac return 0 } complete -F _asdf asdf ================================================ FILE: internal/completions/asdf.elvish ================================================ # Setup argument completions fn arg-completer {|@argz| set argz = $argz[1..-1] # strip 'asdf' and trailing empty string var num = (count $argz) if (== $num 0) { # list all subcommands find $asdf_dir'/lib/commands' -name 'command-*' | each {|cmd| put (re:replace '.*/command-(.*)\.bash' '${1}' $cmd) } put 'plugin' } else { if (match $argz 'current') { # asdf current <name> asdf plugin-list } elif (match $argz 'env') { # asdf env <command> ls-shims } elif (match $argz 'env' '.*') { # asdf env <command> [util] ls-executables } elif (match $argz 'exec') { # asdf exec <command> ls-shims } elif (match $argz 'global') { # asdf global <name> asdf plugin-list } elif (match $argz 'global' '.*') { # asdf global <name> <version> ls-installed-versions $argz[-1] } elif (match $argz 'install') { # asdf install <name> asdf plugin-list } elif (match $argz 'install' '.*') { # asdf install <name> <version> ls-all-versions $argz[-1] } elif (match $argz 'install' '.*' '.*') { # asdf install <name> <version> [--keep-download] put '--keep-download' } elif (match $argz 'latest') { # asdf latest <name> asdf plugin-list } elif (match $argz 'latest' '.*') { # asdf latest <name> [<version>] ls-all-versions $argz[-1] } elif (match $argz 'list-all') { # asdf list all <name> asdf plugin-list } elif (match $argz 'list-all' '.*') { # asdf list all <name> [<version>] ls-all-versions $argz[-1] } elif (match $argz 'list') { # asdf list <name> asdf plugin-list } elif (match $argz 'list' '.*') { # asdf list <name> [<version>] ls-installed-versions $argz[-1] } elif (match $argz 'local') { # asdf local <name> [-p|--parent] asdf plugin-list put '-p' put '--parent' } elif (match $argz 'local' '(-p|(--parent))') { # asdf local <name> [-p|--parent] <version> asdf plugin-list } elif (match $argz 'local' '.*') { # asdf local <name> [-p|--parent] # asdf local <name> <version> ls-installed-versions $argz[-1] put '-p' put '--parent' } elif (match $argz 'local' '(-p|(--parent))' '.*') { # asdf local [-p|--parent] <name> <version> ls-installed-versions $argz[-1] } elif (match $argz 'local' '.*' '(-p|(--parent))') { # asdf local <name> [-p|--parent] <version> ls-installed-versions $argz[-2] } elif (match $argz 'local' '.*' '.*') { # asdf local <name> <version> [-p|--parent] put '-p' put '--parent' } elif (or (match $argz 'plugin-add') (match $argz 'plugin' 'add')) { # asdf plugin add <name> asdf plugin-list-all | each {|line| put (re:replace '([^\s]+)\s+.*' '${1}' $line) } } elif (or (match $argz 'plugin-list') (match $argz 'plugin' 'list')) { # asdf plugin list put '--urls' put '--refs' put 'all' } elif (or (match $argz 'plugin-push') (match $argz 'plugin' 'push')) { # asdf plugin push <name> asdf plugin-list } elif (or (match $argz 'plugin-remove') (match $argz 'plugin' 'remove')) { # asdf plugin remove <name> asdf plugin-list } elif (and (>= (count $argz) 3) (match $argz[..3] 'plugin-test' '.*' '.*')) { # asdf plugin-test <plugin-name> <plugin-url> [--asdf-tool-version <version>] [--asdf-plugin-gitref <git-ref>] [test-command*] put '--asdf-plugin-gitref' put '--asdf-tool-version' ls-executables ls-shims } elif (and (>= (count $argz) 4) (match $argz[..4] 'plugin' 'test' '.*' '.*')) { # asdf plugin test <plugin-name> <plugin-url> [--asdf-tool-version <version>] [--asdf-plugin-gitref <git-ref>] [test-command*] put '--asdf-plugin-gitref' put '--asdf-tool-version' ls-executables ls-shims } elif (or (match $argz 'plugin-update') (match $argz 'plugin' 'update')) { # asdf plugin update <name> asdf plugin-list put '--all' } elif (match $argz 'plugin') { # list plugin-* subcommands find $asdf_dir'/lib/commands' -name 'command-plugin-*' | each {|cmd| put (re:replace '.*/command-plugin-(.*)\.bash' '${1}' $cmd) } } elif (match $argz 'reshim') { # asdf reshim <name> asdf plugin-list } elif (match $argz 'reshim' '.*') { # asdf reshim <name> <version> ls-installed-versions $argz[-1] } elif (match $argz 'shim-versions') { # asdf shim-versions <command> ls-shims } elif (match $argz 'uninstall') { # asdf uninstall <name> asdf plugin-list } elif (match $argz 'uninstall' '.*') { # asdf uninstall <name> <version> ls-installed-versions $argz[-1] } elif (match $argz 'update') { if (== $num 1) { # asdf update put '--head' } } elif (match $argz 'where') { # asdf where <name> asdf plugin-list } elif (match $argz 'where' '.*') { # asdf where <name> [<version>] ls-installed-versions $argz[-1] } elif (match $argz 'which') { ls-shims } } } ================================================ FILE: internal/completions/asdf.fish ================================================ set -x asdf_data_dir ( if test -n "$ASDF_DATA_DIR"; echo $ASDF_DATA_DIR; else; echo $HOME/.asdf; end) function __fish_asdf_needs_command set -l cmd (commandline -opc) if test (count $cmd) -eq 1 return 0 end return 1 end function __fish_asdf_using_command -a current_command set -l cmd (commandline -opc) if test (count $cmd) -gt 1 if test $current_command = $cmd[2] return 0 end end return 1 end function __fish_asdf_arg_number -a number set -l cmd (commandline -opc) test (count $cmd) -eq $number end function __fish_asdf_arg_at -a number set -l cmd (commandline -opc) echo $cmd[$number] end function __fish_asdf_list_versions -a plugin 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 end function __fish_asdf_plugin_list asdf plugin list 2>/dev/null end function __fish_asdf_plugin_list_all asdf plugin list all 2>/dev/null end function __fish_asdf_list_shims path basename $asdf_data_dir/shims/* end # plugin completion complete -f -c asdf -n __fish_asdf_needs_command -a plugin -d "Plugin management sub-commands" # suggest `add` after `plugin` complete -f -c asdf -n '__fish_asdf_using_command plugin; and __fish_asdf_arg_number 2' -a add -d "Add git repo as plugin" # show available plugins after `plugin add` complete -f -c asdf -n '__fish_asdf_using_command plugin; and __fish_asdf_arg_at 2 = "add"; and __fish_asdf_arg_number 3' -a '(__fish_asdf_plugin_list_all | grep -v \'*\' | awk \'{ print $1 }\')' # show repository urls for selected plugin complete -f -c asdf -n '__fish_asdf_using_command plugin; and __fish_asdf_arg_at 2 = "add"; and __fish_asdf_arg_number 4' -a '(__fish_asdf_plugin_list_all | grep (__fish_asdf_arg_at 3) | awk \'{ print $2 }\')' # plugin list completion complete -f -c asdf -n '__fish_asdf_using_command plugin; and __fish_asdf_arg_number 2' -a list -d "List installed plugins" complete -f -c asdf -n '__fish_asdf_using_command plugin; and __fish_asdf_arg_at 2 = "list"; and __fish_asdf_arg_number 3' -a all -d "List all available plugins" # plugin remove completion # show `remove` as an option for `plugin` complete -f -c asdf -n '__fish_asdf_using_command plugin; and __fish_asdf_arg_number 2' -a remove -d "Remove plugin and package versions" # Show list of plugins after `remove` complete -f -c asdf -n '__fish_asdf_using_command plugin; and __fish_asdf_arg_at 2 = "remove"; and __fish_asdf_arg_number 3' -a '(__fish_asdf_plugin_list)' # plugin update completion complete -f -c asdf -n '__fish_asdf_using_command plugin; and __fish_asdf_arg_number 2' -a update -d "Update plugin" # suggest the plugin list complete -f -c asdf -n '__fish_asdf_using_command plugin; and __fish_asdf_arg_at 2 = "update"; and __fish_asdf_arg_number 3' -a '(__fish_asdf_plugin_list)' # suggest '--all' complete -f -c asdf -n '__fish_asdf_using_command plugin; and __fish_asdf_arg_at 2 = "update"; and __fish_asdf_arg_number 3' -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_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_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_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_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_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_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_using_command list; and __fish_asdf_arg_number 2' -a '(__fish_asdf_plugin_list)' # list-all completion complete -f -c asdf -n '__fish_asdf_using_command list; and __fish_asdf_arg_number 2' -a all -d "List all versions of a package" complete -f -c asdf -n '__fish_asdf_using_command list; and __fish_asdf_arg_at 2 = "all"; and __fish_asdf_arg_number 3' -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_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 shimversions -d "List the plugins and versions that provide a command" complete -f -c asdf -n '__fish_asdf_using_command shimversions; and __fish_asdf_arg_number 2' -a '(__fish_asdf_list_shims)' # set completion complete -f -c asdf -n __fish_asdf_needs_command -a set -d "Set version for a plugin" complete -f -c asdf -n '__fish_asdf_using_command set; and __fish_asdf_arg_number 2' -a '(__fish_asdf_plugin_list)' complete -f -c asdf -n '__fish_asdf_using_command set; and test (count (commandline -opc)) -gt 2' -a '(__fish_asdf_list_versions (__fish_asdf_arg_at 3)) system' # set commands complete -f -c asdf -n '__fish_asdf_using_command set' -l home -d "Set version in home directory" complete -f -c asdf -n '__fish_asdf_using_command set' -l parent -d "Set version in parent directory" # 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 "Print the currently installed version of ASDF" ================================================ FILE: internal/completions/asdf.nushell ================================================ module asdf { def "complete asdf sub-commands" [] { [ "plugin", "list", "install", "uninstall", "current", "where", "which", "local", "global", "shell", "latest", "help", "exec", "env", "info", "version", "reshim", "shim-version", "update" ] } def "complete asdf installed" [] { ^asdf plugin list | lines | each { |line| $line | str trim } } def "complete asdf plugin sub-commands" [] { [ "list", "list all", "add", "remove", "update" ] } def "complete asdf installed plugins" [] { ^asdf plugin list | lines | each { |line| $line | str trim } } def "complete asdf plugin versions all" [context: string] { let plugin = $context | str trim | split words | last ^asdf list all $plugin | lines | each { |line| $line | str trim } | prepend "latest" } def "complete asdf plugin versions installed" [context: string] { let plugin = $context | str trim | split words | last let versions = ^asdf list $plugin | lines | each { |line| $line | str trim } | each { |version| if ($version | str starts-with "*") {{value: ($version | str substring 1..), description: "current version"}} else {{value: $version, description: ""}} } let latest = ^asdf latest $plugin | str trim if ($versions | get value | any {|el| $el == $latest}) { $versions | prepend {value: "latest", description: $"alias to ($latest)"} } else { $versions } } # ASDF version manager export extern main [ subcommand?: string@"complete asdf sub-commands" ] # Manage plugins export extern "asdf plugin" [ subcommand?: string@"complete asdf plugin sub-commands" ] # List installed plugins export def "asdf plugin list" [ --urls # Show urls --refs # Show refs ] { let params = [ {name: 'urls', enabled: $urls, flag: '--urls', template: '\s+?(?P<repository>(?:http[s]?|git).+\.git|/.+)'} {name: 'refs', enabled: $refs, flag: '--refs', template: '\s+?(?P<branch>\w+)\s+(?P<ref>\w+)'} ] let template = '(?P<name>.+)' + ( $params | where enabled | get --ignore-errors template | str join '' | str trim ) let flags = ($params | where enabled | get --ignore-errors flag | default '' ) ^asdf plugin list ...$flags | lines | parse -r $template | str trim } # list all available plugins export def "asdf plugin list all" [] { let template = '(?P<name>.+)\s+?(?P<installed>[*]?)(?P<repository>(?:git|http|https).+)' let is_installed = { |it| $it.installed == '*' } ^asdf plugin list all | lines | parse -r $template | str trim | update installed $is_installed | sort-by name } # Add a plugin export extern "asdf plugin add" [ name: string # Name of the plugin git_url?: string # Git url of the plugin ] # Remove an installed plugin and their package versions export extern "asdf plugin remove" [ name: string@"complete asdf installed plugins" # Name of the plugin ] # Update a plugin export extern "asdf plugin update" [ name: string@"complete asdf installed plugins" # Name of the plugin git_ref?: string # Git ref to update the plugin ] # Update all plugins to the latest commit export extern "asdf plugin update --all" [] # install a package version export extern "asdf install" [ name?: string@"complete asdf installed plugins" # Name of the package version?: string@"complete asdf plugin versions all" # Version of the package or latest ] # Remove an installed package version export extern "asdf uninstall" [ name: string@"complete asdf installed" # Name of the package version: string@"complete asdf plugin versions installed" # Version of the package ] # Display current version export extern "asdf current" [ name?: string@"complete asdf installed" # Name of installed version of a package ] # Display path of an executable export extern "asdf which" [ command: string # Name of command ] # Display install path for an installed package version export extern "asdf where" [ name: string@"complete asdf installed" # Name of installed package version?: string@"complete asdf plugin versions installed" # Version of installed package ] # Set the package local version export extern "asdf local" [ name: string@"complete asdf installed" # Name of the package version?: string@"complete asdf plugin versions installed" # Version of the package or latest ] # Set the package global version export extern "asdf global" [ name: string@"complete asdf installed" # Name of the package version?: string@"complete asdf plugin versions installed" # Version of the package or latest ] # Set the package to version in the current shell export extern "asdf shell" [ name: string@"complete asdf installed" # Name of the package version?: string@"complete asdf plugin versions installed" # Version of the package or latest ] # Show latest stable version of a package export extern "asdf latest" [ name: string@"complete asdf installed" # Name of the package version?: string@"complete asdf plugin versions installed" # Filter latest stable version from this version ] # Show latest stable version for all installed packages export extern "asdf latest --all" [] # List installed package versions export extern "asdf list" [ name?: string@"complete asdf installed" # Name of the package version?: string@"complete asdf plugin versions installed" # Filter the version ] # List all available package versions export def "asdf list all" [ name: string@"complete asdf installed" # Name of the package version?: string@"complete asdf plugin versions installed"="" # Filter the version ] { ^asdf list all $name $version | lines | parse "{version}" | str trim } # Show documentation for plugin export extern "asdf help" [ name: string@"complete asdf installed" # Name of the plugin version?: string@"complete asdf plugin versions installed" # Version of the plugin ] # Execute a command shim for the current version export extern "asdf exec" [ command: string # Name of the command ...args: any # Arguments to pass to the command ] # Run util (default: env) inside the environment used for command shim execution export extern "asdf env" [ command?: string # Name of the command util?: string = 'env' # Name of util to run ] # Show information about OS, Shell and asdf Debug export extern "asdf info" [] # Print the currently installed version of ASDF export extern "asdf version" [] # Recreate shims for version package export extern "asdf reshim" [ name?: string@"complete asdf installed" # Name of the package version?: string@"complete asdf plugin versions installed" # Version of the package ] # List the plugins and versions that provide a command export extern "asdf shim-version" [ command: string # Name of the command ] # Update asdf to the latest version on the stable branch export extern "asdf update" [] # Update asdf to the latest version on the main branch export extern "asdf update --head" [] } use asdf * ================================================ FILE: internal/completions/asdf.zsh ================================================ #compdef asdf #description tool to manage versions of multiple runtimes # Initialize local variables for ZSH completion context local state subcmd # Set asdf directory path, using ASDF_DATA_DIR if set, otherwise default to ~/.asdf local asdf_dir="${ASDF_DATA_DIR:-$HOME/.asdf}" # Define plugin management commands local -a asdf_plugin_commands asdf_plugin_commands=( 'add:add plugin from asdf-plugins repo or from git URL' 'list:list installed plugins (--urls with URLs)' 'remove:remove named plugin and all packages for it' 'update:update named plugin (or --all)' ) # Define main asdf commands array with descriptions local -a asdf_commands asdf_commands=( # 'asdf help' lists commands with help text # Plugin related commands 'plugin:plugin management sub-commands' # tools 'help:Output documentation for plugin and tool' 'install:install tool at stated version, or all from .tools-versions' 'uninstall:remove a specific version of a tool' 'current:display current versions for named tool (else all)' 'latest:display latest version available to install for a named tool' 'where:display install path for given tool at optional specified version' 'which:display path to an executable' 'set:Set a tool version in a .tool-version file' 'list:list installed versions of a tool' # Utility commands 'exec:executes the command shim for the current version' 'env:prints or runs an executable under a command environment' 'info:print os, shell and asdf debug information' 'version:print the currently installed version of ASDF' 'reshim:recreate shims for version of a tool' 'shim:shim management sub-commands' 'shimversions:list for given command which plugins and versions provide it' ) # Function to list all available plugins from the repository _asdf__available_plugins() { local plugin_dir="${asdf_dir:?}/repository/plugins" if [[ ! -d "$plugin_dir" ]]; then _wanted asdf-available-plugins expl 'ASDF Installable Plugins' \ compadd -x "no plugins repository found" return fi local -a plugins plugins=( "$plugin_dir"/*(:t) ) _wanted asdf-available-plugins expl 'ASDF Installable Plugins' \ compadd -a plugins } # Function to list currently installed plugins _asdf__installed_plugins() { local plugin_dir="${asdf_dir:?}/plugins" if [[ ! -d "$plugin_dir" ]]; then _wanted asdf-plugins expl 'ASDF Plugins' \ compadd -x "no plugins dir, none installed yet" return fi local -a plugins plugins=( "$plugin_dir"/*(:t) ) _wanted asdf-plugins expl 'ASDF Plugins' \ compadd -a plugins } # Function to list installed versions for a specific plugin _asdf__installed_versions_of() { local plugin_dir="${asdf_dir:?}/installs/${1:?need a plugin version}" if [[ ! -d "$plugin_dir" ]]; then _wanted "asdf-versions-$1" expl "ASDF Plugin ${(q-)1} versions" \ compadd -x "no versions installed" return fi local -a versions versions=( "$plugin_dir"/*(:t) ) _wanted "asdf-versions-$1" expl "ASDF Plugin ${(q-)1} versions" \ compadd -a versions } # Similar to _asdf__installed_versions_of but includes 'system' as an option _asdf__installed_versions_of_plus_system() { local plugin_dir="${asdf_dir:?}/installs/${1:?need a plugin version}" if [[ ! -d "$plugin_dir" ]]; then _wanted "asdf-versions-$1" expl "ASDF Plugin ${(q-)1} versions" \ compadd -x "no versions installed" return fi local -a versions versions=( "$plugin_dir"/*(:t) ) versions+="system" _wanted "asdf-versions-$1" expl "ASDF Plugin ${(q-)1} versions" \ compadd -a versions } # Function to get available git references for a plugin _asdf__plugin_git_refs() { local plugin=$1 local data_dir=${ASDF_DATA_DIR:-$HOME/.asdf} local plugin_path="$data_dir/plugins/$plugin" if [[ -d "$plugin_path/.git" ]]; then # Get remote branches and format them git -C "$plugin_path" branch -r 2> /dev/null | \ sed \ -e 's/^[[:space:]]*[^\/]*\///' \ -e 's/[[:space:]]*->.*$//' \ -e 's/\(.*\)/\1:Remote branch \1/' | \ sort -fd # Get tags and format them git -C "$plugin_path" tag 2> /dev/null | \ sed -e 's/\(.*\)/\1:Tag \1/' | \ sort -V # Get recent commit hashes and messages (last 10 commits) git -C "$plugin_path" log --pretty=format:'%h:%s' -n 10 2> /dev/null fi } # Handle top-level command completion first if (( CURRENT == 2 )); then _arguments -C : '--version[version]' ':command:->command' fi # Process command state for top-level commands case "$state" in (command) _describe -t asdf-commands 'ASDF Commands' asdf_commands return ;; esac # Get the subcommand for further processing subcmd="${words[2]}" # Complex completion logic for each subcommand # Each case handles specific completion scenarios for the respective command case "$subcmd" in (plugin) # Handle plugin subcommand completions with nested subcommands if (( CURRENT == 3 )); then _describe -t asdf-plugin-commands 'ASDF Plugin Commands' asdf_plugin_commands else local plugin_subcmd="${words[3]}" case "$plugin_subcmd" in (add) # Complete available plugins or URLs for add command if (( CURRENT == 4 )); then _asdf__available_plugins elif (( CURRENT == 5 )); then _arguments "*:${words[4]} plugin url:_urls" fi return ;; (update) # Handle update command with support for --all flag and git refs if (( CURRENT == 4 )); then _alternative \ 'flags:flags:((--all\:"Update all installed plugins"))' \ 'asdf-available-plugins:Installed ASDF Plugins:_asdf__installed_plugins' elif (( CURRENT == 5 )); then if [[ ${words[4]} != "--all" ]]; then local -a refs while IFS=: read -r value descr; do refs+=( "${value}:${descr}" ) done < <(_asdf__plugin_git_refs ${words[4]}) _describe -V -t git-refs 'Git References' refs fi fi ;; (remove) # Complete installed plugin names for remove command _asdf__installed_plugins return ;; (list) # Handle list command options with support for --urls and --refs flags case $CURRENT in 4) _alternative \ 'flags:flags:((--urls\:"Show repository URLs" --refs\:"Show Git references"))' \ 'commands:commands:((all\:"List all available plugins"))' return ;; 5) # Handle remaining available flags if [[ ${words[4]} == --* ]]; then local used_flags=("${words[@]}") local -a available_flags available_flags=() if [[ ! "${used_flags[@]}" =~ "--urls" ]]; then available_flags+=("--urls") fi if [[ ! "${used_flags[@]}" =~ "--refs" ]]; then available_flags+=("--refs") fi (( ${#available_flags[@]} )) && compadd -- "${available_flags[@]}" fi return ;; esac ;; esac fi ;; (current) # Complete with installed plugins for current command _asdf__installed_plugins ;; (list) # Handle list command completions with support for 'all' and specific plugins case $CURRENT in 3) _alternative \ 'commands:commands:((all\:"List all available (remote) versions"))' \ 'plugin:plugin:_asdf__installed_plugins' ;; 4) if [[ ${words[3]} == "all" ]]; then _asdf__installed_plugins else # For normal list: show installed versions with optional filter _asdf__installed_versions_of ${words[3]} fi ;; 5) # When listing all versions of a specific plugin if [[ ${words[3]} == "all" ]]; then local versions if versions=$(asdf list all "${words[4]}" 2>/dev/null); then _wanted "remote-versions-${words[4]}" \ expl "Available versions of ${words[4]}" \ compadd -- ${(f)versions} else _message "Unable to fetch versions for ${words[4]}" fi fi ;; esac ;; (help) # Complete installed plugins and their versions for help command if (( CURRENT == 3 )); then _asdf__installed_plugins elif (( CURRENT == 4 )); then _asdf__installed_versions_of ${words[3]} fi ;; (install) # Handle complex install command completion with latest tag support if (( CURRENT == 3 )); then _asdf__installed_plugins return elif (( CURRENT == 4 )); then local tool="${words[3]}" local ver_prefix="${words[4]}" if [[ $ver_prefix == latest:* ]]; then # Handle latest:<version> syntax _wanted "latest-versions-$tool" \ expl "Latest version" \ compadd -- latest:${^$(asdf list all "$tool")} else # Offer both latest options and specific versions _wanted "latest-tag-$tool" \ expl "Latest version" \ compadd -- 'latest' 'latest:' _wanted "remote-versions-$tool" \ expl "Available versions of $tool" \ compadd -- $(asdf list all "$tool") fi return fi ;; (latest) # Complete plugin names or --all flag for latest command if (( CURRENT == 3 )); then _alternative \ 'flags:flags:((--all\:"Show latest version of all tools"))' \ 'asdf-available-plugins:Installed ASDF Plugins:_asdf__installed_plugins' fi ;; (uninstall|reshim|where) # Handle complex install command completion with latest tag support if (( CURRENT == 3 )); then _asdf__installed_plugins return elif (( CURRENT == 4 )); then # For normal list: show installed versions with optional filter _asdf__installed_versions_of ${words[3]} return fi ;; (set) # Handle set command completion case $CURRENT in 3) _alternative \ 'flags:flags:((-u\:"set version in user home directory" -p\:"set version in closest parent .tool-versions"))' \ 'plugin:plugin:_asdf__installed_plugins' ;; 4) if [[ ${words[3]} == -* ]]; then # After flag, complete with plugin name _asdf__installed_plugins else # Complete with installed versions for the plugin _asdf__installed_versions_of_plus_system ${words[3]} fi ;; *) # Support for multiple version specifications if [[ ${words[3]} == -* ]]; then local plugin="${words[4]}" else local plugin="${words[3]}" fi _asdf__installed_versions_of_plus_system $plugin ;; esac ;; (which|shimversions) # Complete with available shims for which and shimversions commands _wanted asdf-shims expl "ASDF Shims" compadd -- "${asdf_dir:?}/shims"/*(:t) ;; (exec) # Handle exec command completion with shim command and args if (( CURRENT == 3 )); then _wanted asdf-shims expl "ASDF Shims" compadd -- "${asdf_dir:?}/shims"/*(:t) else compset -n 3 _normal -p "asdf-shims-${words[3]}" fi ;; (env) # Handle env command completion with shim name and arbitrary command if (( CURRENT == 3 )); then _wanted asdf-shims expl "ASDF Shims" compadd -- "${asdf_dir:?}/shims"/*(:t) else compset -n 4 _normal -p "asdf-shims-${words[3]}" fi ;; esac ================================================ FILE: internal/completions/completions.go ================================================ // Package completions handles shell completion files. // // To add completion support for a shell, simply add a file named // "asdf.<shell>" to this directory, replacing "<shell>" with the name // of the shell. package completions import ( "embed" "errors" "io/fs" "slices" "strings" ) //go:embed asdf.* var completions embed.FS // Get returns a file containing completion code for the given shell if it is // found. func Get(name string) (fs.File, bool) { file, err := completions.Open("asdf." + name) if err != nil { if errors.Is(err, fs.ErrNotExist) { return nil, false } panic(err) // This should never happen. } return file, true } // Names returns a slice of shell names that completion is available for. func Names() []string { files, _ := fs.Glob(completions, "asdf.*") for i, file := range files { files[i] = strings.TrimPrefix(file, "asdf.") } slices.Sort(files) return files } ================================================ FILE: internal/completions/completions_test.go ================================================ package completions import ( "testing" "github.com/stretchr/testify/assert" ) func TestGet(t *testing.T) { t.Run("returns file when completion file found with matching name", func(t *testing.T) { file, found := Get("bash") info, err := file.Stat() assert.Nil(t, err) assert.Equal(t, "asdf.bash", info.Name()) assert.True(t, found) }) t.Run("returns false when completion file not found", func(t *testing.T) { _, found := Get("non-existent") assert.False(t, found) }) } func TestNames(t *testing.T) { t.Run("returns slice of shell names for which completion is available", func(t *testing.T) { assert.Equal(t, []string{"bash", "elvish", "fish", "nushell", "zsh"}, Names()) }) } ================================================ FILE: internal/config/config.go ================================================ // Package config provides a unified API for fetching asdf config. Either from // the asdfrc file or environment variables. package config import ( "io/fs" "os" "path/filepath" "runtime" "strconv" "strings" "gopkg.in/ini.v1" ) const ( dataDirDefault = "~/.asdf" configFileDefault = "~/.asdfrc" defaultToolVersionsFilenameDefault = ".tool-versions" defaultPluginIndexURL = "https://github.com/asdf-vm/asdf-plugins.git" ) /* PluginRepoCheckDuration represents the remote plugin repo check duration * (never or every N seconds). It's not clear to me how this should be * represented in Golang so using a struct for maximum flexibility. */ type PluginRepoCheckDuration struct { Never bool Every int } var pluginRepoCheckDurationDefault = PluginRepoCheckDuration{Every: 60} // Config is the primary value this package builds and returns type Config struct { Home string ConfigFile string DefaultToolVersionsFilename string DataDir string Settings Settings PluginIndexURL string } // Settings is a struct that stores config values from the asdfrc file type Settings struct { Loaded bool Raw *ini.Section LegacyVersionFile bool // I don't think this setting should be supported in the Golang implementation // UseReleaseCandidates bool AlwaysKeepDownload bool PluginRepositoryLastCheckDuration PluginRepoCheckDuration DisablePluginShortNameRepository bool Concurrency string } func defaultConfig(dataDir, configFile string) *Config { return &Config{ DataDir: dataDir, ConfigFile: configFile, DefaultToolVersionsFilename: defaultToolVersionsFilenameDefault, PluginIndexURL: defaultPluginIndexURL, } } func defaultSettings() *Settings { return &Settings{ Loaded: false, Raw: nil, LegacyVersionFile: false, AlwaysKeepDownload: false, PluginRepositoryLastCheckDuration: pluginRepoCheckDurationDefault, DisablePluginShortNameRepository: false, Concurrency: getConcurrency("auto"), } } func newPluginRepoCheckDuration(checkDuration string) PluginRepoCheckDuration { if strings.ToLower(checkDuration) == "never" { return PluginRepoCheckDuration{Never: true} } every, err := strconv.Atoi(checkDuration) if err != nil { // if error parsing config use default value return pluginRepoCheckDurationDefault } return PluginRepoCheckDuration{Every: every} } // LoadConfig builds the Config struct from environment variables func LoadConfig() (Config, error) { config := defaultConfig(dataDirDefault, configFileDefault) homeDir, err := os.UserHomeDir() if err != nil { return Config{}, err } configFile := os.Getenv("ASDF_CONFIG_FILE") if configFile != "" { config.ConfigFile = configFile } dataDir := os.Getenv("ASDF_DATA_DIR") if dataDir != "" { config.DataDir = dataDir } versionFilename := os.Getenv("ASDF_TOOL_VERSIONS_FILENAME") if versionFilename != "" { config.DefaultToolVersionsFilename = versionFilename } else { // ASDF_TOOL_VERSIONS_FILENAME is the new environment variable name. It used // to be named ASDF_DEFAULT_TOOL_VERSIONS_FILENAME versionFilename = os.Getenv("ASDF_DEFAULT_TOOL_VERSIONS_FILENAME") if versionFilename != "" { config.DefaultToolVersionsFilename = versionFilename } } config.Home = homeDir config.DataDir = normalizePath(homeDir, config.DataDir) config.ConfigFile = normalizePath(homeDir, config.ConfigFile) return *config, nil } // Methods on the Config struct that allow it to load and cache values from the // Settings struct, which is loaded from file on disk and therefore somewhat // "expensive". // LegacyVersionFile loads the asdfrc if it isn't already loaded and fetches // the legacy version file support flag func (c *Config) LegacyVersionFile() (bool, error) { err := c.loadSettings() if err != nil { return false, err } return c.Settings.LegacyVersionFile, nil } // AlwaysKeepDownload loads the asdfrc if it isn't already loaded and fetches // the keep downloads boolean flag func (c *Config) AlwaysKeepDownload() (bool, error) { err := c.loadSettings() if err != nil { return false, err } return c.Settings.AlwaysKeepDownload, nil } // PluginRepositoryLastCheckDuration loads the asdfrc if it isn't already loaded // and fetches the keep boolean flag func (c *Config) PluginRepositoryLastCheckDuration() (PluginRepoCheckDuration, error) { err := c.loadSettings() if err != nil { return newPluginRepoCheckDuration(""), err } return c.Settings.PluginRepositoryLastCheckDuration, nil } // DisablePluginShortNameRepository loads the asdfrc if it isn't already loaded // and fetches the disable plugin short name repo flag func (c *Config) DisablePluginShortNameRepository() (bool, error) { err := c.loadSettings() if err != nil { return false, err } return c.Settings.DisablePluginShortNameRepository, nil } // Concurrency returns concurrency setting from asdfrc file func (c *Config) Concurrency() (string, error) { err := c.loadSettings() if err != nil { return getConcurrency("auto"), err } return c.Settings.Concurrency, nil } // GetHook returns a hook command from config if it is there func (c *Config) GetHook(hook string) (string, error) { err := c.loadSettings() if err != nil { return "", err } if c.Settings.Raw != nil { return c.Settings.Raw.Key(hook).String(), nil } return "", nil } func (c *Config) loadSettings() error { if c.Settings.Loaded { return nil } settings, err := loadSettings(c.ConfigFile) c.Settings = settings if err != nil { _, ok := err.(*fs.PathError) if ok { return nil } return err } return nil } func normalizePath(homeDir string, path string) string { if path == "~" || strings.HasPrefix(path, "~/") { path = filepath.Join(homeDir, path[1:]) } return filepath.Clean(path) } func loadSettings(asdfrcPath string) (Settings, error) { settings := defaultSettings() // asdfrc is effectively formatted as ini config, err := ini.Load(asdfrcPath) if err != nil { return *settings, err } mainConf := config.Section("") settings.Raw = mainConf settings.Loaded = true settings.PluginRepositoryLastCheckDuration = newPluginRepoCheckDuration(mainConf.Key("plugin_repository_last_check_duration").String()) boolOverride(&settings.LegacyVersionFile, mainConf, "legacy_version_file") boolOverride(&settings.AlwaysKeepDownload, mainConf, "always_keep_download") boolOverride(&settings.DisablePluginShortNameRepository, mainConf, "disable_plugin_short_name_repository") concurrency := strings.ToLower(mainConf.Key("concurrency").String()) if concurrency != "" { settings.Concurrency = getConcurrency(concurrency) } return *settings, nil } func boolOverride(field *bool, section *ini.Section, key string) { lcYesOrNo := strings.ToLower(section.Key(key).String()) if lcYesOrNo == "yes" { *field = true } if lcYesOrNo == "no" { *field = false } } func getConcurrency(concurrency string) string { concurrencyFromEnv := strings.ToLower(os.Getenv("ASDF_CONCURRENCY")) if concurrencyFromEnv != "" { concurrency = concurrencyFromEnv } if concurrency == "auto" || concurrency == "" { return strconv.Itoa(runtime.NumCPU()) } return concurrency } ================================================ FILE: internal/config/config_test.go ================================================ package config import ( "os" "runtime" "strconv" "strings" "testing" "github.com/stretchr/testify/assert" ) func TestLoadConfig(t *testing.T) { t.Run("With defaults", func(t *testing.T) { config, err := LoadConfig() assert.Nil(t, err, "Returned error when loading env for config") homeDir, err := os.UserHomeDir() assert.Nil(t, err) assert.Equal(t, homeDir, config.Home, "Home directory has the wrong value") assert.True(t, strings.HasPrefix(config.DataDir, homeDir), "DataDir has the wrong value") assert.True(t, strings.HasPrefix(config.ConfigFile, homeDir)) }) t.Run("With ASDF_DATA_DIR containing a tilde", func(t *testing.T) { t.Setenv("ASDF_DATA_DIR", "~/some/other/dir") config, err := LoadConfig() assert.Nil(t, err, "Returned error when loading env for config") homeDir, err := os.UserHomeDir() assert.Nil(t, err) assert.Equal(t, homeDir, config.Home, "Home directory has the wrong value") assert.Equal(t, homeDir+"/some/other/dir", config.DataDir, "DataDir has the wrong value") assert.True(t, strings.HasPrefix(config.ConfigFile, homeDir)) }) } func TestLoadSettings(t *testing.T) { t.Run("When given invalid path returns error", func(t *testing.T) { settings, err := loadSettings("./foobar") if err == nil { t.Fatal("Didn't get an error") } if settings.Loaded { t.Fatal("Didn't expect settings to be loaded") } }) t.Run("When given path to populated asdfrc returns populated settings struct", func(t *testing.T) { settings, err := loadSettings("testdata/asdfrc") assert.Nil(t, err) assert.True(t, settings.Loaded, "Expected Loaded field to be set to true") assert.True(t, settings.LegacyVersionFile, "LegacyVersionFile field has wrong value") assert.True(t, settings.AlwaysKeepDownload, "AlwaysKeepDownload field has wrong value") assert.True(t, settings.PluginRepositoryLastCheckDuration.Never, "PluginRepositoryLastCheckDuration field has wrong value") assert.Zero(t, settings.PluginRepositoryLastCheckDuration.Every, "PluginRepositoryLastCheckDuration field has wrong value") assert.True(t, settings.DisablePluginShortNameRepository, "DisablePluginShortNameRepository field has wrong value") assert.Equal(t, "5", settings.Concurrency, "Concurrency field has wrong value") }) t.Run("ASDF_CONCURRENCY=99 takes precedence over asdfrc value", func(t *testing.T) { t.Setenv("ASDF_CONCURRENCY", "99") settings, err := loadSettings("testdata/asdfrc") assert.Nil(t, err) assert.True(t, settings.Loaded, "Expected Loaded field to be set to true") assert.Equal(t, "99", settings.Concurrency, "Concurrency field has wrong value") }) t.Run("ASDF_CONCURRENCY=auto takes precedence over asdfrc value", func(t *testing.T) { expectedConcurrency := strconv.Itoa(runtime.NumCPU()) t.Setenv("ASDF_CONCURRENCY", "auto") settings, err := loadSettings("testdata/asdfrc") assert.Nil(t, err) assert.True(t, settings.Loaded, "Expected Loaded field to be set to true") assert.Equal(t, expectedConcurrency, settings.Concurrency, "Concurrency field has wrong value") }) t.Run("When given path to empty file returns settings struct with defaults", func(t *testing.T) { expectedConcurrency := strconv.Itoa(runtime.NumCPU()) settings, err := loadSettings("testdata/empty-asdfrc") assert.Nil(t, err) assert.False(t, settings.LegacyVersionFile, "LegacyVersionFile field has wrong value") assert.False(t, settings.AlwaysKeepDownload, "AlwaysKeepDownload field has wrong value") assert.False(t, settings.PluginRepositoryLastCheckDuration.Never, "PluginRepositoryLastCheckDuration field has wrong value") assert.Equal(t, settings.PluginRepositoryLastCheckDuration.Every, 60, "PluginRepositoryLastCheckDuration field has wrong value") assert.False(t, settings.DisablePluginShortNameRepository, "DisablePluginShortNameRepository field has wrong value") assert.Equal(t, expectedConcurrency, settings.Concurrency, "Concurrency field has wrong value") }) } func TestConfigMethods(t *testing.T) { // Set the asdf config file location to the test file t.Setenv("ASDF_CONFIG_FILE", "testdata/asdfrc") config, err := LoadConfig() assert.Nil(t, err, "Returned error when building config") t.Run("Returns LegacyVersionFile from asdfrc file", func(t *testing.T) { legacyFile, err := config.LegacyVersionFile() assert.Nil(t, err, "Returned error when loading settings") assert.True(t, legacyFile, "Expected LegacyVersionFile to be set") }) t.Run("Returns AlwaysKeepDownload from asdfrc file", func(t *testing.T) { alwaysKeepDownload, err := config.AlwaysKeepDownload() assert.Nil(t, err, "Returned error when loading settings") assert.True(t, alwaysKeepDownload, "Expected AlwaysKeepDownload to be set") }) t.Run("Returns PluginRepositoryLastCheckDuration from asdfrc file", func(t *testing.T) { checkDuration, err := config.PluginRepositoryLastCheckDuration() assert.Nil(t, err, "Returned error when loading settings") assert.True(t, checkDuration.Never, "Expected PluginRepositoryLastCheckDuration to be set") assert.Zero(t, checkDuration.Every, "Expected PluginRepositoryLastCheckDuration to be set") }) t.Run("Returns DisablePluginShortNameRepository from asdfrc file", func(t *testing.T) { DisablePluginShortNameRepository, err := config.DisablePluginShortNameRepository() assert.Nil(t, err, "Returned error when loading settings") assert.True(t, DisablePluginShortNameRepository, "Expected DisablePluginShortNameRepository to be set") }) t.Run("When file does not exist returns settings struct with defaults", func(t *testing.T) { config := Config{ConfigFile: "non-existent"} legacy, err := config.LegacyVersionFile() assert.Nil(t, err) assert.False(t, legacy) keepDownload, err := config.AlwaysKeepDownload() assert.Nil(t, err) assert.False(t, keepDownload) lastCheck, err := config.PluginRepositoryLastCheckDuration() assert.Nil(t, err) assert.False(t, lastCheck.Never) checkDuration, err := config.PluginRepositoryLastCheckDuration() assert.Nil(t, err) assert.Equal(t, checkDuration.Every, 60) shortName, err := config.DisablePluginShortNameRepository() assert.Nil(t, err) assert.False(t, shortName) }) } func TestConfigGetHook(t *testing.T) { // Set the asdf config file location to the test file t.Setenv("ASDF_CONFIG_FILE", "testdata/asdfrc") config, err := LoadConfig() assert.Nil(t, err, "Returned error when building config") t.Run("Returns empty string when hook not present in asdfrc file", func(t *testing.T) { hookCmd, err := config.GetHook("post_asdf_plugin_add") assert.Nil(t, err) assert.Zero(t, hookCmd) }) t.Run("Returns string containing Bash expression when present in asdfrc file", func(t *testing.T) { hookCmd, err := config.GetHook("pre_asdf_plugin_add") assert.Nil(t, err) assert.Equal(t, hookCmd, "echo Executing with args: $@") }) t.Run("Ignores trailing and leading spaces", func(t *testing.T) { hookCmd, err := config.GetHook("pre_asdf_plugin_add_test") assert.Nil(t, err) assert.Equal(t, hookCmd, "echo Executing with args: $@") }) t.Run("Preserves quoting", func(t *testing.T) { hookCmd, err := config.GetHook("pre_asdf_plugin_add_test2") assert.Nil(t, err) assert.Equal(t, hookCmd, "echo 'Executing' \"with args: $@\"") }) t.Run("works if no config file", func(t *testing.T) { config := Config{} hookCmd, err := config.GetHook("some_hook") assert.Nil(t, err) assert.Empty(t, hookCmd) }) } ================================================ FILE: internal/config/testdata/asdfrc ================================================ # This is a test asdfrc file containing all possible values. Each field to set # to a value that is different than the default. legacy_version_file = yes use_release_candidates = yes always_keep_download = yes plugin_repository_last_check_duration = never disable_plugin_short_name_repository = yes concurrency = 5 # Hooks pre_asdf_plugin_add = echo Executing with args: $@ pre_asdf_plugin_add_test = echo Executing with args: $@ pre_asdf_plugin_add_test2 = echo 'Executing' "with args: $@" ================================================ FILE: internal/config/testdata/empty-asdfrc ================================================ ================================================ FILE: internal/data/data.go ================================================ // Package data provides constants and functions pertaining to directories and // files in the asdf data directory on disk, specified by the $ASDF_DATA_DIR package data import ( "path/filepath" ) const ( dataDirDownloads = "downloads" dataDirInstalls = "installs" dataDirPlugins = "plugins" ) // DownloadDirectory returns the directory a plugin will be placing // downloads of version source code func DownloadDirectory(dataDir, pluginName string) string { return filepath.Join(dataDir, dataDirDownloads, pluginName) } // InstallDirectory returns the path to a plugin directory func InstallDirectory(dataDir, pluginName string) string { return filepath.Join(dataDir, dataDirInstalls, pluginName) } // PluginsDirectory returns the path to the plugins directory in the data dir func PluginsDirectory(dataDir string) string { return filepath.Join(dataDir, dataDirPlugins) } // PluginDirectory returns the directory a plugin with a given name would be in // if it were installed func PluginDirectory(dataDir, pluginName string) string { return filepath.Join(dataDir, dataDirPlugins, pluginName) } ================================================ FILE: internal/data/data_test.go ================================================ package data import "testing" const testPluginName = "lua" func TestPluginDirectory(t *testing.T) { t.Run("returns new path with plugin name as last segment", func(t *testing.T) { pluginDir := PluginDirectory("~/.asdf/", testPluginName) expected := "~/.asdf/plugins/lua" if pluginDir != expected { t.Errorf("got %v, expected %v", pluginDir, expected) } }) } ================================================ FILE: internal/exec/exec.go ================================================ // Package exec handles replacing the asdf go process with package exec import ( "syscall" ) // Exec invokes syscall.Exec to exec an executable. Requires an absolute path to // executable. func Exec(executablePath string, args []string, env []string) error { return syscall.Exec(executablePath, append([]string{executablePath}, args...), env) } ================================================ FILE: internal/exec/exec_test.go ================================================ package exec import ( "fmt" "os" "os/exec" "testing" "github.com/rogpeppe/go-internal/testscript" ) func execit() { // Exec only works with absolute path cmdPath, _ := exec.LookPath(os.Args[1]) err := Exec(cmdPath, os.Args[2:], os.Environ()) if err != nil { fmt.Printf("Err: %#+v\n", err.Error()) } } func TestMain(m *testing.M) { testscript.Main(m, map[string]func() { "execit": execit, }) } func TestExec(t *testing.T) { testscript.Run(t, testscript.Params{ Dir: "testdata/script", }) } ================================================ FILE: internal/exec/testdata/script/exec-env.txtar ================================================ env ENV=foo execit echo this is a $ENV stdout 'this is a foo\n' ================================================ FILE: internal/exec/testdata/script/exec-simple.txtar ================================================ execit echo this is a test stdout 'this is a test\n' ================================================ FILE: internal/execenv/execenv.go ================================================ // Package execenv contains logic for generating execution environing using a plugin's // exec-env callback script if available. package execenv import ( "fmt" "strings" "github.com/asdf-vm/asdf/internal/execute" "github.com/asdf-vm/asdf/internal/plugins" ) const execEnvCallbackName = "exec-env" // Generate runs exec-env callback if available and captures the environment // variables it sets. It then parses them and returns them as a map. func Generate(plugin plugins.Plugin, callbackEnv map[string]string) (env map[string]string, err error) { execEnvPath, err := plugin.CallbackPath(execEnvCallbackName) if err != nil { return callbackEnv, err } var stdout strings.Builder // This is done to support the legacy behavior. exec-env is the only asdf // callback that works by exporting environment variables. Because of this, // executing the callback isn't enough. We actually need to source it (.) so // the environment variables get set, and then run `env` so they get printed // to STDOUT. expression := execute.NewExpression(fmt.Sprintf(". \"%s\"; env -0", execEnvPath), []string{}) expression.Env = callbackEnv expression.Stdout = &stdout err = expression.Run() str := stdout.String() return execute.SliceToMap(strings.Split(str, "\x00")), err } ================================================ FILE: internal/execenv/execenv_test.go ================================================ package execenv import ( "testing" "github.com/asdf-vm/asdf/internal/config" "github.com/asdf-vm/asdf/internal/plugins" "github.com/asdf-vm/asdf/internal/repotest" "github.com/stretchr/testify/assert" ) const ( testPluginName = "lua" testPluginName2 = "ruby" ) func TestGenerate(t *testing.T) { t.Run("returns map of environment variables", func(t *testing.T) { testDataDir := t.TempDir() conf := config.Config{DataDir: testDataDir} _, err := repotest.InstallPlugin("dummy_plugin", testDataDir, testPluginName) assert.Nil(t, err) plugin := plugins.New(conf, testPluginName) assert.Nil(t, repotest.WritePluginCallback(plugin.Dir, "exec-env", "#!/usr/bin/env bash\nexport BAZ=bar")) env, err := Generate(plugin, map[string]string{"ASDF_INSTALL_VERSION": "test"}) assert.Nil(t, err) assert.Equal(t, "bar", env["BAZ"]) assert.Equal(t, "test", env["ASDF_INSTALL_VERSION"]) }) t.Run("returns error when plugin lacks exec-env callback", func(t *testing.T) { testDataDir := t.TempDir() conf := config.Config{DataDir: testDataDir} _, err := repotest.InstallPlugin("dummy_plugin", testDataDir, testPluginName2) assert.Nil(t, err) plugin := plugins.New(conf, testPluginName2) env, err := Generate(plugin, map[string]string{}) assert.Equal(t, err.(plugins.NoCallbackError).Error(), "Plugin named ruby does not have a callback named exec-env") _, found := env["FOO"] assert.False(t, found) }) t.Run("preserves environment variables that contain equals sign in value", func(t *testing.T) { testDataDir := t.TempDir() conf := config.Config{DataDir: testDataDir} _, err := repotest.InstallPlugin("dummy_plugin", testDataDir, testPluginName) assert.Nil(t, err) plugin := plugins.New(conf, testPluginName) assert.Nil(t, repotest.WritePluginCallback(plugin.Dir, "exec-env", "#!/usr/bin/env bash\nexport BAZ=bar")) env, err := Generate(plugin, map[string]string{"EQUALSTEST": "abc=123"}) assert.Nil(t, err) assert.Equal(t, "bar", env["BAZ"]) assert.Equal(t, "abc=123", env["EQUALSTEST"]) }) t.Run("preserves environment variables that contain equals sign in value", func(t *testing.T) { testDataDir := t.TempDir() conf := config.Config{DataDir: testDataDir} _, err := repotest.InstallPlugin("dummy_plugin", testDataDir, testPluginName) assert.Nil(t, err) plugin := plugins.New(conf, testPluginName) assert.Nil(t, repotest.WritePluginCallback(plugin.Dir, "exec-env", "#!/usr/bin/env bash\nexport BAZ=bar")) env, err := Generate(plugin, map[string]string{"EQUALSTEST": "abc\n123"}) assert.Nil(t, err) assert.Equal(t, "bar", env["BAZ"]) assert.Equal(t, "abc\n123", env["EQUALSTEST"]) }) t.Run("preserves environment variables that contain equals sign and line breaks in value", func(t *testing.T) { value := "-----BEGIN CERTIFICATE-----\nMANY\\LINES\\THE\nLAST\\ONE\\ENDS\\IN\nAN=\n-----END CERTIFICATE-----" testDataDir := t.TempDir() conf := config.Config{DataDir: testDataDir} _, err := repotest.InstallPlugin("dummy_plugin", testDataDir, testPluginName) assert.Nil(t, err) plugin := plugins.New(conf, testPluginName) assert.Nil(t, repotest.WritePluginCallback(plugin.Dir, "exec-env", "#!/usr/bin/env bash\nexport BAZ=\""+value+"\"")) env, err := Generate(plugin, map[string]string{}) assert.Nil(t, err) assert.Equal(t, value, env["BAZ"]) }) } ================================================ FILE: internal/execute/execute.go ================================================ // Package execute is a simple package that wraps the os/exec Command features // for convenient use in asdf. It was inspired by // https://github.com/chen-keinan/go-command-eval package execute import ( "fmt" "io" "os" "os/exec" "strings" ) // Command represents a Bash command that can be executed by asdf type Command struct { Command string Expression string Args []string Stdin io.Reader Stdout io.Writer Stderr io.Writer Env map[string]string } // New takes a string containing the path to a Bash script, and a slice of // string arguments and returns a Command struct func New(command string, args []string) Command { return Command{Command: command, Args: args} } // NewExpression takes a string containing a Bash expression and a slice of // string arguments and returns a Command struct func NewExpression(expression string, args []string) Command { return Command{Expression: expression, Args: args} } // Run executes a Command with Bash and returns the error if there is one func (c Command) Run() error { var command string if c.Expression != "" { // Expressions need to be invoked inside a Bash function, so variables like // $0 and $@ are available command = fmt.Sprintf("fn() { %s; }; fn %s", c.Expression, formatArgString(c.Args)) } else { // Scripts can be invoked directly, with args provided command = fmt.Sprintf("%s %s", c.Command, formatArgString(c.Args)) } cmd := exec.Command("bash", "-c", command) if len(c.Env) > 0 { cmd.Env = MergeWithCurrentEnv(c.Env) } else { cmd.Env = os.Environ() } cmd.Stdin = c.Stdin // Capture stdout and stderr cmd.Stdout = c.Stdout cmd.Stderr = c.Stderr return cmd.Run() } // MergeWithCurrentEnv merges the provided map into the current environment variables func MergeWithCurrentEnv(env map[string]string) (slice []string) { return MapToSlice(MergeEnv(CurrentEnv(), env)) } // CurrentEnv returns the current environment as a map func CurrentEnv() map[string]string { return SliceToMap(os.Environ()) } // MergeEnv takes two maps with string keys and values and merges them. func MergeEnv(map1, map2 map[string]string) map[string]string { for key, value := range map2 { map1[key] = value } return map1 } // MapToSlice converts an env map to env slice suitable for syscall.Exec func MapToSlice(env map[string]string) (slice []string) { for key, value := range env { slice = append(slice, fmt.Sprintf("%s=%s", key, value)) } return slice } // SliceToMap converts an env map to env slice suitable for syscall.Exec func SliceToMap(env []string) map[string]string { envMap := map[string]string{} for _, envVar := range env { varValue := strings.SplitN(envVar, "=", 2) if len(varValue) == 2 { envMap[varValue[0]] = varValue[1] } } return envMap } func formatArgString(args []string) string { var newArgs []string for _, str := range args { newArgs = append(newArgs, fmt.Sprintf("\"%s\"", str)) } return strings.Join(newArgs, " ") } ================================================ FILE: internal/execute/execute_test.go ================================================ package execute import ( "fmt" "os" "os/exec" "strings" "testing" "github.com/stretchr/testify/assert" ) func TestNew(t *testing.T) { t.Run("Returns new command", func(t *testing.T) { cmd := New("echo", []string{"test string"}) assert.Equal(t, "echo", cmd.Command) assert.Equal(t, "", cmd.Expression) }) } func TestNewExpression(t *testing.T) { t.Run("Returns new command expression", func(t *testing.T) { cmd := NewExpression("echo", []string{"test string"}) assert.Equal(t, "echo", cmd.Expression) assert.Equal(t, "", cmd.Command) }) } func TestRun_Command(t *testing.T) { t.Run("command is executed with bash", func(t *testing.T) { // "type" (bash) output is locale sensitive cmd := New("echo $(LANG=C type -a sh);", []string{}) var stdout strings.Builder cmd.Stdout = &stdout err := cmd.Run() assert.Nil(t, err) assert.Contains(t, stdout.String(), "sh is /") }) t.Run("positional arg is passed to command", func(t *testing.T) { cmd := New("testdata/script", []string{"test string"}) var stdout strings.Builder cmd.Stdout = &stdout err := cmd.Run() assert.Nil(t, err) assert.Equal(t, "test string\n", stdout.String()) }) t.Run("positional args are passed to command", func(t *testing.T) { cmd := New("testdata/script", []string{"test string", "another string"}) var stdout strings.Builder cmd.Stdout = &stdout err := cmd.Run() assert.Nil(t, err) assert.Equal(t, "test string another string\n", stdout.String()) }) t.Run("environment variables are passed to command", func(t *testing.T) { cmd := New("echo $MYVAR;", []string{}) cmd.Env = map[string]string{"MYVAR": "my var value"} var stdout strings.Builder cmd.Stdout = &stdout err := cmd.Run() assert.Nil(t, err) assert.Equal(t, "my var value\n", stdout.String()) }) t.Run("system environment variables are passed to command", func(t *testing.T) { cmd := New("echo $MYVAR1;", []string{}) err := os.Setenv("MYVAR1", "my var value") assert.Nil(t, err) var stdout strings.Builder cmd.Stdout = &stdout err = cmd.Run() assert.Nil(t, err) assert.Equal(t, "my var value\n", stdout.String()) }) t.Run("provided env overwrites system environment variables when passed to command", func(t *testing.T) { cmd := New("echo $MYVAR2;", []string{}) err := os.Setenv("MYVAR2", "should be dropped") assert.Nil(t, err) var stdout strings.Builder cmd.Stdout = &stdout cmd.Env = map[string]string{"MYVAR2": "final value"} err = cmd.Run() assert.Nil(t, err) assert.Equal(t, "final value\n", stdout.String()) }) t.Run("captures stdout and stdin", func(t *testing.T) { cmd := New("echo 'a test' | tee /dev/stderr", []string{}) cmd.Env = map[string]string{"MYVAR": "my var value"} var stdout strings.Builder cmd.Stdout = &stdout var stderr strings.Builder cmd.Stderr = &stderr err := cmd.Run() assert.Nil(t, err) assert.Equal(t, "a test\n", stdout.String()) assert.Equal(t, "a test\n", stderr.String()) }) t.Run("returns error when non-zero exit code", func(t *testing.T) { cmd := New("exit 12", []string{}) var stdout strings.Builder cmd.Stdout = &stdout err := cmd.Run() assert.NotNil(t, err) assert.Equal(t, "", stdout.String()) assert.Equal(t, 12, err.(*exec.ExitError).ExitCode()) }) } func TestRun_Expression(t *testing.T) { t.Run("expression is executed with bash", func(t *testing.T) { // "type" (bash) output is locale sensitive cmd := NewExpression("echo $(LANG=C type -a sh)", []string{}) var stdout strings.Builder cmd.Stdout = &stdout err := cmd.Run() assert.Nil(t, err) assert.Contains(t, stdout.String(), "sh is /") }) t.Run("positional arg is passed to expression", func(t *testing.T) { cmd := NewExpression("echo $1; true", []string{"test string"}) var stdout strings.Builder cmd.Stdout = &stdout err := cmd.Run() assert.Nil(t, err) assert.Equal(t, "test string\n", stdout.String()) }) t.Run("positional args are passed to expression", func(t *testing.T) { cmd := NewExpression("echo $@; true", []string{"test string", "another string"}) var stdout strings.Builder cmd.Stdout = &stdout err := cmd.Run() assert.Nil(t, err) assert.Equal(t, "test string another string\n", stdout.String()) }) t.Run("environment variables are passed to expression", func(t *testing.T) { cmd := NewExpression("echo $MYVAR", []string{}) cmd.Env = map[string]string{"MYVAR": "my var value"} var stdout strings.Builder cmd.Stdout = &stdout err := cmd.Run() assert.Nil(t, err) assert.Equal(t, "my var value\n", stdout.String()) }) t.Run("captures stdout and stdin", func(t *testing.T) { cmd := NewExpression("echo 'a test' | tee /dev/stderr", []string{}) cmd.Env = map[string]string{"MYVAR": "my var value"} var stdout strings.Builder cmd.Stdout = &stdout var stderr strings.Builder cmd.Stderr = &stderr err := cmd.Run() assert.Nil(t, err) assert.Equal(t, "a test\n", stdout.String()) assert.Equal(t, "a test\n", stderr.String()) }) t.Run("returns error when non-zero exit code", func(t *testing.T) { cmd := NewExpression("exit 12", []string{}) var stdout strings.Builder cmd.Stdout = &stdout err := cmd.Run() assert.NotNil(t, err) assert.Equal(t, "", stdout.String()) assert.Equal(t, 12, err.(*exec.ExitError).ExitCode()) }) } func TestMergeWithCurrentEnv(t *testing.T) { t.Run("merge with current env", func(t *testing.T) { path := os.Getenv("PATH") assert.NotEmpty(t, path) newEnv := map[string]string{"PATH": "new_path"} mergedEnv := MergeWithCurrentEnv(newEnv) assert.Contains(t, mergedEnv, "PATH=new_path") assert.NotContains(t, mergedEnv, "PATH="+path) }) } func TestCurrentEnv(t *testing.T) { t.Run("returns map of current environment", func(t *testing.T) { envMap := CurrentEnv() path, found := envMap["PATH"] assert.True(t, found) assert.NotEmpty(t, path) }) } func TestMergeEnv(t *testing.T) { t.Run("merges two maps", func(t *testing.T) { map1 := map[string]string{"Key": "value"} map2 := map[string]string{"Key2": "value2"} map3 := MergeEnv(map1, map2) assert.Equal(t, map3["Key"], "value") assert.Equal(t, map3["Key2"], "value2") }) t.Run("doesn't change original map", func(t *testing.T) { map1 := map[string]string{"Key": "value"} map2 := map[string]string{"Key2": "value2"} _ = MergeEnv(map1, map2) assert.Equal(t, map1["Key2"], "value2") }) t.Run("second map overwrites values in first", func(t *testing.T) { map1 := map[string]string{"Key": "value"} map2 := map[string]string{"Key": "value2"} map3 := MergeEnv(map1, map2) assert.Equal(t, map3["Key"], "value2") }) } func TestSliceToMap(t *testing.T) { tests := []struct { input []string output map[string]string }{ { input: []string{"VAR=value"}, output: map[string]string{"VAR": "value"}, }, { input: []string{"BASH_FUNC_bats_readlinkf%%=() { readlink -f \"$1\"\n}"}, output: map[string]string{"BASH_FUNC_bats_readlinkf%%": "() { readlink -f \"$1\"\n}"}, }, { input: []string{"MYVAR=some things = with = in it"}, output: map[string]string{"MYVAR": "some things = with = in it"}, }, { input: []string{"MYVAR=value\nwith\nnewlines"}, output: map[string]string{"MYVAR": "value\nwith\nnewlines"}, }, } for _, tt := range tests { t.Run(fmt.Sprintf("input: %s, output: %s", tt.input, tt.output), func(t *testing.T) { assert.Equal(t, tt.output, SliceToMap(tt.input)) }) } } ================================================ FILE: internal/execute/testdata/script ================================================ #!/usr/bin/env bash echo $@ ================================================ FILE: internal/git/git.go ================================================ // Package git contains all the Git operations that can be applied to asdf // Git repositories like the plugin index repo and individual asdf plugins. package git import ( "errors" "fmt" "io/fs" "os" "strings" "github.com/asdf-vm/asdf/internal/execute" ) // DefaultRemoteName for Git repositories in asdf const DefaultRemoteName = "origin" // Repoer is an interface for operations that can be applied to asdf plugins. // Right now we only support Git, but in the future we might have other // mechanisms to install and upgrade plugins. asdf doesn't require a plugin // to be a Git repository when asdf uses it, but Git is the only way to install // and upgrade plugins. If other approaches are supported this will be // extracted into the `plugins` module. type Repoer interface { Clone(pluginURL, ref string) error Head() (string, error) RemoteURL() (string, error) Update(ref string) (string, string, string, error) } // Repo is a struct to contain the Git repository details type Repo struct { Directory string URL string } // NewRepo builds a new Repo instance func NewRepo(directory string) Repo { return Repo{Directory: directory} } // Clone installs a plugin via Git func (r Repo) Clone(pluginURL, ref string) error { cmdStr := []string{"git", "clone", pluginURL, r.Directory} if ref != "" { cmdStr = []string{"git", "clone", pluginURL, r.Directory, "--branch", ref} } _, stderr, err := exec(cmdStr) if err != nil { return fmt.Errorf("unable to clone plugin: %s", stdErrToErrMsg(stderr)) } return nil } // Head returns the current HEAD ref of the plugin's Git repository func (r Repo) Head() (string, error) { err := repositoryExists(r.Directory) if err != nil { return "", err } stdout, stderr, err := exec([]string{"git", "-C", r.Directory, "rev-parse", "HEAD"}) if err != nil { return "", errors.New(stdErrToErrMsg(stderr)) } return strings.TrimSpace(stdout), nil } // RemoteURL returns the URL of the default remote for the plugin's Git repository func (r Repo) RemoteURL() (string, error) { err := repositoryExists(r.Directory) if err != nil { return "", err } remote, err := r.defaultRemote() if err != nil { return "", err } stdout, _, err := exec([]string{"git", "-C", r.Directory, "remote", "get-url", remote}) return stdout, err } // Update updates the plugin's Git repository to the ref if provided, or the // latest commit on the current branch func (r Repo) Update(ref string) (string, string, string, error) { shortRef := ref oldHash, err := r.Head() if err != nil { return "", "", "", err } remoteName, err := r.defaultRemote() if err != nil { return "", "", "", err } // If no ref is provided we take the default branch of the remote if strings.TrimSpace(ref) == "" { ref, err = r.remoteDefaultBranch() if err != nil { return "", "", "", err } shortRef = strings.SplitN(ref, "/", 3)[2] } commonOpts := []string{"git", "-C", r.Directory} refSpec := fmt.Sprintf("%s:%s", shortRef, shortRef) cmdStr := append(commonOpts, []string{"fetch", "--prune", "--update-head-ok", remoteName, refSpec}...) _, stderr, err := exec(cmdStr) if err != nil { return "", "", "", errors.New(stdErrToErrMsg(stderr)) } cmdStr = append(commonOpts, []string{"-c", "advice.detachedHead=false", "checkout", "--force", shortRef}...) _, stderr, err = exec(cmdStr) if err != nil { return "", "", "", errors.New(stdErrToErrMsg(stderr)) } newHash, err := r.Head() if err != nil { return ref, oldHash, newHash, fmt.Errorf("unable to resolve plugin new Git HEAD: %w", err) } return ref, oldHash, newHash, nil } func (r Repo) defaultRemote() (string, error) { stdout, _, err := exec([]string{"git", "-C", r.Directory, "remote"}) if err != nil { return "", err } return strings.SplitN(stdout, "\n", 2)[0], nil } func (r Repo) remoteDefaultBranch() (string, error) { remote, err := r.defaultRemote() if err != nil { return "", err } stdout, stderr, err := exec([]string{"git", "-C", r.Directory, "ls-remote", "--symref", remote, "HEAD"}) if err != nil { return "", errors.New(stdErrToErrMsg(stderr)) } return strings.Fields(strings.Split(stdout, "\n")[0])[1], nil } func stdErrToErrMsg(stdErr string) string { lines := strings.Split(strings.TrimSuffix(stdErr, "\n"), "\n") return lines[len(lines)-1] } func exec(command []string) (string, string, error) { cmd := execute.New(command[0], command[1:]) var stdOut strings.Builder var stdErr strings.Builder cmd.Stdout = &stdOut cmd.Stderr = &stdErr err := cmd.Run() return stdOut.String(), stdErr.String(), err } func repositoryExists(directory string) error { stat, err := os.Stat(directory) if err != nil { err, _ := err.(*fs.PathError) return err.Err } if stat.IsDir() { // directory exists stdout, _, _ := exec([]string{"git", "-C", directory, "rev-parse", "--is-inside-work-tree"}) if strings.TrimSpace(stdout) == "true" { return nil } return errors.New("not a git repository") } return errors.New("not a directory") } ================================================ FILE: internal/git/git_test.go ================================================ package git import ( "os" "path/filepath" "testing" "github.com/asdf-vm/asdf/internal/repotest" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/stretchr/testify/assert" ) func TestRepoClone(t *testing.T) { t.Setenv("LANG", "C") // error messages from git are locale dependent t.Run("when repo name is valid but URL is invalid prints an error", func(t *testing.T) { repo := NewRepo(t.TempDir()) err := repo.Clone("foobar", "") assert.ErrorContains(t, err, "unable to clone plugin: fatal: repository 'foobar' does not exist") }) t.Run("clones provided Git URL to repo directory when URL is valid", func(t *testing.T) { repoDir := generateRepo(t) directory := t.TempDir() repo := NewRepo(directory) err := repo.Clone(repoDir, "") assert.Nil(t, err) // Assert repo directory contains Git repo with bin directory _, err = os.ReadDir(directory + "/.git") assert.Nil(t, err) entries, err := os.ReadDir(directory + "/bin") assert.Nil(t, err) assert.Equal(t, 12, len(entries)) }) t.Run("when repo name and URL are valid but ref is invalid prints an error", func(t *testing.T) { repoDir := generateRepo(t) directory := t.TempDir() repo := NewRepo(directory) err := repo.Clone(repoDir, "non-existent") assert.ErrorContains(t, err, "unable to clone plugin: fatal: Remote branch non-existent not found in upstream origin") }) t.Run("clones a provided Git URL and checks out a specific ref when URL is valid and ref is provided", func(t *testing.T) { repoDir := generateRepo(t) directory := t.TempDir() repo := NewRepo(directory) err := repo.Clone(repoDir, "master") assert.Nil(t, err) // Assert repo directory contains Git repo with bin directory _, err = os.ReadDir(directory + "/.git") assert.Nil(t, err) entries, err := os.ReadDir(directory + "/bin") assert.Nil(t, err) assert.Equal(t, 12, len(entries)) }) } func TestRepoHead(t *testing.T) { repoDir := generateRepo(t) directory := t.TempDir() repo := NewRepo(directory) err := repo.Clone(repoDir, "") assert.Nil(t, err) head, err := repo.Head() assert.Nil(t, err) assert.NotZero(t, head) } func TestRepoRemoteURL(t *testing.T) { repoDir := generateRepo(t) directory := t.TempDir() repo := NewRepo(directory) err := repo.Clone(repoDir, "") assert.Nil(t, err) url, err := repo.RemoteURL() assert.Nil(t, err) assert.NotZero(t, url) } func TestRepoUpdate(t *testing.T) { t.Setenv("LANG", "C") // error messages from git are locale dependent repoDir := generateRepo(t) directory := t.TempDir() repo := NewRepo(directory) err := repo.Clone(repoDir, "") assert.Nil(t, err) t.Run("returns error when repo with name does not exist", func(t *testing.T) { nonexistentPath := filepath.Join(directory, "nonexistent") nonexistentRepo := NewRepo(nonexistentPath) updatedToRef, _, _, err := nonexistentRepo.Update("") assert.NotNil(t, err) assert.Equal(t, updatedToRef, "") assert.ErrorContains(t, err, "no such file or directory") }) t.Run("returns error when repo repo does not exist", func(t *testing.T) { badRepoDir := t.TempDir() badRepo := NewRepo(badRepoDir) updatedToRef, _, _, err := badRepo.Update("") assert.NotNil(t, err) assert.Equal(t, updatedToRef, "") expectedErrMsg := "not a git repository" assert.ErrorContains(t, err, expectedErrMsg) }) t.Run("does not return error when repo is already updated", func(t *testing.T) { // update repo twice to test already updated case updatedToRef, _, _, err := repo.Update("") assert.Nil(t, err) updatedToRef2, oldHash, newHash, err := repo.Update("") assert.Nil(t, err) assert.Equal(t, updatedToRef, updatedToRef2) assert.Equal(t, oldHash, newHash) }) t.Run("updates repo when repo when repo exists", func(t *testing.T) { latestHash, err := getCurrentCommit(directory) assert.Nil(t, err) _, err = checkoutPreviousCommit(directory) assert.Nil(t, err) updatedToRef, _, _, err := repo.Update("") assert.Nil(t, err) assert.Equal(t, "refs/heads/master", updatedToRef) currentHash, err := getCurrentCommit(directory) assert.Nil(t, err) assert.Equal(t, latestHash, currentHash) }) t.Run("updates repo while leaving untracked files in place", func(t *testing.T) { latestHash, err := getCurrentCommit(directory) assert.Nil(t, err) _, err = checkoutPreviousCommit(directory) assert.Nil(t, err) untrackedDir := filepath.Join(directory, "untracked") err = os.Mkdir(untrackedDir, 0o777) assert.Nil(t, err) expectedContent := []byte("dummy_content") err = os.WriteFile(filepath.Join(untrackedDir, "file_one"), expectedContent, 0o777) assert.Nil(t, err) err = os.WriteFile(filepath.Join(untrackedDir, "file_two"), expectedContent, 0o777) assert.Nil(t, err) updatedToRef, _, _, err := repo.Update("") assert.Nil(t, err) assert.Equal(t, "refs/heads/master", updatedToRef) currentHash, err := getCurrentCommit(directory) assert.Nil(t, err) assert.Equal(t, latestHash, currentHash) content, err := os.ReadFile(filepath.Join(untrackedDir, "file_one")) assert.Nil(t, err) assert.Equal(t, expectedContent, content) content, err = os.ReadFile(filepath.Join(untrackedDir, "file_two")) assert.Nil(t, err) assert.Equal(t, expectedContent, content) }) t.Run("Returns error when specified ref does not exist", func(t *testing.T) { ref := "non-existent" updatedToRef, _, _, err := repo.Update(ref) assert.Equal(t, updatedToRef, "") expectedErrMsg := "fatal: couldn't find remote ref non-existent" assert.ErrorContains(t, err, expectedErrMsg) }) t.Run("updates repo to ref when repo with name and ref exist", func(t *testing.T) { ref := "master" hash, err := getCommit(directory, ref) assert.Nil(t, err) updatedToRef, _, newHash, err := repo.Update(ref) assert.Nil(t, err) assert.Equal(t, "master", updatedToRef) // Check that repo was updated to ref latestHash, err := getCurrentCommit(directory) assert.Nil(t, err) assert.Equal(t, hash, latestHash) assert.Equal(t, newHash, latestHash) }) } func getCurrentCommit(path string) (string, error) { return getCommit(path, "HEAD") } func getCommit(path, revision string) (string, error) { repo, err := git.PlainOpen(path) if err != nil { return "", err } hash, err := repo.ResolveRevision(plumbing.Revision(revision)) return hash.String(), err } func checkoutPreviousCommit(path string) (string, error) { repo, err := git.PlainOpen(path) if err != nil { return "", err } previousHash, err := repo.ResolveRevision(plumbing.Revision("HEAD~")) if err != nil { return "", err } worktree, err := repo.Worktree() if err != nil { return "", err } err = worktree.Reset(&git.ResetOptions{Commit: *previousHash}) if err != nil { return "", err } return previousHash.String(), nil } func generateRepo(t *testing.T) string { t.Helper() tempDir := t.TempDir() path, err := repotest.GeneratePlugin("dummy_plugin", tempDir, "lua") assert.Nil(t, err) return path } ================================================ FILE: internal/help/help.go ================================================ // Package help contains functions responsible for generating help output for // asdf and asdf plugins. package help import ( _ "embed" "fmt" "io" "os" "strings" "github.com/asdf-vm/asdf/internal/config" "github.com/asdf-vm/asdf/internal/plugins" "github.com/asdf-vm/asdf/internal/toolversions" ) //go:embed help.txt var helpText string const quote = "\"Late but latest\"\n-- Rajinikanth" // Print help output to STDOUT func Print(asdfVersion string, plugins []plugins.Plugin) error { return Write(asdfVersion, plugins, os.Stdout) } // PrintTool write tool help output to STDOUT func PrintTool(conf config.Config, toolName string) error { return WriteToolHelp(conf, toolName, os.Stdout, os.Stderr) } // PrintToolVersion write help for specific tool version to STDOUT func PrintToolVersion(conf config.Config, toolName, toolVersion string) error { return WriteToolVersionHelp(conf, toolName, toolVersion, os.Stdout, os.Stderr) } // Write help output to an io.Writer func Write(asdfVersion string, allPlugins []plugins.Plugin, writer io.Writer) error { _, err := writer.Write([]byte(fmt.Sprintf("version: %s\n\n", asdfVersion))) if err != nil { return err } _, err = writer.Write([]byte(helpText)) if err != nil { return err } _, err = writer.Write([]byte("\n")) if err != nil { return err } extensionCommandHelp, err := pluginExtensionCommands(allPlugins) if err != nil { fmt.Printf("err %#+v\n", err) return err } _, err = writer.Write([]byte(extensionCommandHelp)) if err != nil { return err } _, err = writer.Write([]byte("\n")) if err != nil { return err } _, err = writer.Write([]byte(quote)) if err != nil { return err } _, err = writer.Write([]byte("\n")) if err != nil { return err } return nil } // WriteToolHelp output to an io.Writer func WriteToolHelp(conf config.Config, toolName string, writer io.Writer, errWriter io.Writer) error { return writePluginHelp(conf, toolName, "", writer, errWriter) } // WriteToolVersionHelp output to an io.Writer func WriteToolVersionHelp(conf config.Config, toolName, toolVersion string, writer io.Writer, errWriter io.Writer) error { return writePluginHelp(conf, toolName, toolVersion, writer, errWriter) } func writePluginHelp(conf config.Config, toolName, toolVersion string, writer io.Writer, errWriter io.Writer) error { plugin := plugins.New(conf, toolName) env := map[string]string{ "ASDF_INSTALL_PATH": plugin.Dir, } if toolVersion != "" { version := toolversions.Parse(toolVersion) env["ASDF_INSTALL_VERSION"] = version.Value env["ASDF_INSTALL_TYPE"] = version.Type } if err := plugin.Exists(); err != nil { errWriter.Write([]byte(fmt.Sprintf("No plugin named %s\n", plugin.Name))) return err } err := plugin.RunCallback("help.overview", []string{}, env, writer, errWriter) if _, ok := err.(plugins.NoCallbackError); ok { // No such callback, print err msg errWriter.Write([]byte(fmt.Sprintf("No documentation for plugin %s\n", plugin.Name))) return err } if err != nil { return err } err = plugin.RunCallback("help.deps", []string{}, env, writer, errWriter) if _, ok := err.(plugins.NoCallbackError); !ok { return err } err = plugin.RunCallback("help.config", []string{}, env, writer, errWriter) if _, ok := err.(plugins.NoCallbackError); !ok { return err } err = plugin.RunCallback("help.links", []string{}, env, writer, errWriter) if _, ok := err.(plugins.NoCallbackError); !ok { return err } return nil } func pluginExtensionCommands(plugins []plugins.Plugin) (string, error) { var output strings.Builder for _, plugin := range plugins { commands, err := plugin.GetExtensionCommands() if err != nil { return output.String(), err } if len(commands) > 0 { output.WriteString(fmt.Sprintf("PLUGIN %s\n", plugin.Name)) for _, command := range commands { if command == "" { // must be default command output.WriteString(fmt.Sprintf(" asdf %s\n", plugin.Name)) } else { output.WriteString(fmt.Sprintf(" asdf %s %s\n", plugin.Name, command)) } } } } return output.String(), nil } ================================================ FILE: internal/help/help.txt ================================================ MANAGE PLUGINS asdf plugin add <name> [<git-url>] Add a plugin from the plugin repo OR, add a Git repo as a plugin by specifying the name and repo url asdf plugin list [--urls] [--refs] List installed plugins. Optionally show git urls and git-ref asdf plugin list all List plugins registered on asdf-plugins repository with URLs asdf plugin remove <name> Remove plugin and package versions asdf plugin update <name> [<git-ref>] Update a plugin to latest commit on default branch or a particular git-ref asdf plugin update --all Update all plugins to latest commit on default branch MANAGE TOOLS asdf current Display current version set or being used for all packages asdf current <name> Display current version set or being used for package asdf help <name> [<version>] Output documentation for plugin and tool asdf install Install all the package versions listed in the .tool-versions file asdf install <name> Install one tool at the version specified in the .tool-versions file asdf install <name> <version> Install a specific version of a package asdf install <name> latest[:<version>] Install the latest stable version of a package, or with optional version, install the latest stable version that begins with the given string asdf latest <name> [<version>] Show latest stable version of a package asdf latest --all Show latest stable version of all the packages and if they are installed asdf list <name> [version] List installed versions of a package and optionally filter the versions asdf list all <name> [<version>] List all versions of a package and optionally filter the returned versions asdf set [-u] [-p] <name> <versions...> Set a tool version in a .tool-version in the current directory, or a parent directory. asdf uninstall <name> <version> Remove a specific version of a package asdf where <name> [<version>] Display install path for an installed or current version asdf which <command> Display the path to an executable UTILS 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. asdf info Print OS, Shell and ASDF debug information. asdf version Print the currently installed version of ASDF asdf reshim <name> <version> Recreate shims for version of a package asdf shimversions <command> List the plugins and versions that provide a command RESOURCES GitHub: https://github.com/asdf-vm/asdf Docs: https://asdf-vm.com ================================================ FILE: internal/help/help_test.go ================================================ package help import ( "fmt" "os" "path/filepath" "strings" "testing" "github.com/asdf-vm/asdf/internal/config" "github.com/asdf-vm/asdf/internal/plugins" "github.com/asdf-vm/asdf/internal/repotest" "github.com/stretchr/testify/assert" ) const ( version = "0.15.0" testPluginName = "lua" ) func TestWrite(t *testing.T) { testDataDir := t.TempDir() conf := config.Config{DataDir: testDataDir} err := os.MkdirAll(filepath.Join(testDataDir, "plugins"), 0o777) assert.Nil(t, err) // install dummy plugin _, err = repotest.InstallPlugin("dummy_plugin", testDataDir, testPluginName) assert.Nil(t, err) plugin := plugins.New(conf, testPluginName) writeExtensionCommand(t, plugin, "", "") var stdout strings.Builder err = Write(version, []plugins.Plugin{plugin}, &stdout) assert.Nil(t, err) output := stdout.String() // Simple format assertions assert.Contains(t, output, "version: ") assert.Contains(t, output, "MANAGE PLUGINS\n") assert.Contains(t, output, "MANAGE TOOLS\n") assert.Contains(t, output, "UTILS\n") assert.Contains(t, output, "RESOURCES\n") assert.Contains(t, output, "PLUGIN lua\n") } func TestWriteToolHelp(t *testing.T) { conf, plugin := generateConfig(t) t.Run("when plugin implements all help callbacks", func(t *testing.T) { var stdout strings.Builder var stderr strings.Builder err := WriteToolHelp(conf, plugin.Name, &stdout, &stderr) assert.Nil(t, err) assert.Empty(t, stderr.String()) expected := "Dummy plugin documentation\n\nDummy plugin is a plugin only used for unit tests\n" assert.Equal(t, stdout.String(), expected) }) t.Run("when plugin does not have help.overview callback", func(t *testing.T) { var stdout strings.Builder var stderr strings.Builder plugin := installPlugin(t, conf, "dummy_legacy_plugin", "legacy-plugin") err := WriteToolHelp(conf, plugin.Name, &stdout, &stderr) assert.EqualError(t, err, "Plugin named legacy-plugin does not have a callback named help.overview") assert.Empty(t, stdout.String()) assert.Equal(t, stderr.String(), "No documentation for plugin legacy-plugin\n") }) t.Run("when plugin does not exist", func(t *testing.T) { var stdout strings.Builder var stderr strings.Builder err := WriteToolHelp(conf, "non-existent", &stdout, &stderr) assert.EqualError(t, err, "Plugin named non-existent not installed") assert.Empty(t, stdout.String()) assert.Equal(t, stderr.String(), "No plugin named non-existent\n") }) } func TestWriteToolVersionHelp(t *testing.T) { conf, plugin := generateConfig(t) t.Run("when plugin implements all help callbacks", func(t *testing.T) { var stdout strings.Builder var stderr strings.Builder err := WriteToolVersionHelp(conf, plugin.Name, "1.2.3", &stdout, &stderr) assert.Nil(t, err) assert.Empty(t, stderr.String()) expected := "Dummy plugin documentation\n\nDummy plugin is a plugin only used for unit tests\n\nDetails specific for version 1.2.3\n" assert.Equal(t, stdout.String(), expected) }) t.Run("when plugin does not have help.overview callback", func(t *testing.T) { var stdout strings.Builder var stderr strings.Builder plugin := installPlugin(t, conf, "dummy_legacy_plugin", "legacy-plugin") err := WriteToolVersionHelp(conf, plugin.Name, "1.2.3", &stdout, &stderr) assert.EqualError(t, err, "Plugin named legacy-plugin does not have a callback named help.overview") assert.Empty(t, stdout.String()) assert.Equal(t, stderr.String(), "No documentation for plugin legacy-plugin\n") }) t.Run("when plugin does not exist", func(t *testing.T) { var stdout strings.Builder var stderr strings.Builder err := WriteToolVersionHelp(conf, "non-existent", "1.2.3", &stdout, &stderr) assert.EqualError(t, err, "Plugin named non-existent not installed") assert.Empty(t, stdout.String()) assert.Equal(t, stderr.String(), "No plugin named non-existent\n") }) } func generateConfig(t *testing.T) (config.Config, plugins.Plugin) { t.Helper() testDataDir := t.TempDir() conf, err := config.LoadConfig() assert.Nil(t, err) conf.DataDir = testDataDir return conf, installPlugin(t, conf, "dummy_plugin", testPluginName) } func installPlugin(t *testing.T, conf config.Config, fixture, pluginName string) plugins.Plugin { _, err := repotest.InstallPlugin(fixture, conf.DataDir, pluginName) assert.Nil(t, err) return plugins.New(conf, pluginName) } func writeExtensionCommand(t *testing.T, plugin plugins.Plugin, name, contents string) error { t.Helper() assert.Nil(t, os.MkdirAll(filepath.Join(plugin.Dir, "lib", "commands"), 0o777)) filename := "command" if name != "" { filename = fmt.Sprintf("command-%s", name) } path := filepath.Join(plugin.Dir, "lib", "commands", filename) err := os.WriteFile(path, []byte(contents), 0o777) return err } ================================================ FILE: internal/hook/hook.go ================================================ // Package hook provides a simple interface for running hook commands that may // be defined in the asdfrc file package hook import ( "io" "os" "github.com/asdf-vm/asdf/internal/config" "github.com/asdf-vm/asdf/internal/execute" ) // Run gets a hook command from config and runs it with the provided arguments. // Output is sent to STDOUT and STDERR func Run(conf config.Config, hookName string, arguments []string) error { return RunWithOutput(conf, hookName, arguments, os.Stdout, os.Stderr) } // RunWithOutput gets a hook command from config and runs it with the provided // arguments. Output is sent to the provided io.Writers. func RunWithOutput(config config.Config, hookName string, arguments []string, stdOut io.Writer, stdErr io.Writer) error { hookCmd, err := config.GetHook(hookName) if err != nil { return err } if hookCmd == "" { return nil } cmd := execute.NewExpression(hookCmd, arguments) cmd.Stdout = stdOut cmd.Stderr = stdErr return cmd.Run() } ================================================ FILE: internal/hook/hook_test.go ================================================ package hook import ( "os/exec" "testing" "github.com/asdf-vm/asdf/internal/config" "github.com/stretchr/testify/assert" ) func TestRun(t *testing.T) { // Set the asdf config file location to the test file t.Setenv("ASDF_CONFIG_FILE", "testdata/asdfrc") t.Run("accepts config, hook name, and a slice of string arguments", func(t *testing.T) { config, err := config.LoadConfig() assert.Nil(t, err) err = Run(config, "pre_asdf_plugin_add_test", []string{}) assert.Nil(t, err) }) t.Run("passes argument to command", func(t *testing.T) { config, err := config.LoadConfig() assert.Nil(t, err) err = Run(config, "pre_asdf_plugin_add_test2", []string{"123"}) assert.Equal(t, 123, err.(*exec.ExitError).ExitCode()) }) t.Run("passes arguments to command", func(t *testing.T) { config, err := config.LoadConfig() assert.Nil(t, err) err = Run(config, "pre_asdf_plugin_add_test3", []string{"exit 123"}) assert.Equal(t, 123, err.(*exec.ExitError).ExitCode()) }) t.Run("does not return error when no such hook is defined in asdfrc", func(t *testing.T) { config, err := config.LoadConfig() assert.Nil(t, err) err = Run(config, "nonexistent-hook", []string{}) assert.Nil(t, err) }) } ================================================ FILE: internal/hook/testdata/asdfrc ================================================ # This is a test asdfrc file containing all possible values. Each field to set # to a value that is different than the default. # Hooks pre_asdf_plugin_add = echo Executing with args: $@ pre_asdf_plugin_add_test = echo Executing with args: $@ pre_asdf_plugin_add_test2 = exit $1 pre_asdf_plugin_add_test3 = eval $@ ================================================ FILE: internal/info/info.go ================================================ // Package info exists to print important info about this asdf installation to STDOUT for use in debugging and bug reports. package info import ( "fmt" "io" "os" "text/tabwriter" "github.com/asdf-vm/asdf/internal/config" "github.com/asdf-vm/asdf/internal/execute" "github.com/asdf-vm/asdf/internal/plugins" ) // Print info output to STDOUT func Print(conf config.Config, version string) error { return Write(conf, version, os.Stdout) } // Write info output to an io.Writer func Write(conf config.Config, version string, writer io.Writer) error { fmt.Fprintln(writer, "OS:") uname := execute.NewExpression("uname -a", []string{}) uname.Stdout = writer err := uname.Run() if err != nil { return err } fmt.Fprintln(writer, "\nSHELL:") shellVersion := execute.NewExpression("$SHELL --version", []string{}) shellVersion.Stdout = writer err = shellVersion.Run() if err != nil { return err } fmt.Fprintln(writer, "\nBASH VERSION:") bashVersion := execute.NewExpression("echo $BASH_VERSION", []string{}) bashVersion.Stdout = writer err = bashVersion.Run() if err != nil { return err } fmt.Fprintln(writer, "\nASDF VERSION:") fmt.Fprintf(writer, "%s\n", version) fmt.Fprintln(writer, "\nASDF INTERNAL VARIABLES:") fmt.Fprintf(writer, "ASDF_TOOL_VERSIONS_FILENAME=%s\n", conf.DefaultToolVersionsFilename) fmt.Fprintf(writer, "ASDF_DATA_DIR=%s\n", conf.DataDir) fmt.Fprintf(writer, "ASDF_CONFIG_FILE=%s\n", conf.ConfigFile) fmt.Fprintln(writer, "\nASDF INSTALLED PLUGINS:") plugins, err := plugins.List(conf, true, true) if err != nil { fmt.Fprintf(writer, "error loading plugin list: %s", err) return err } pluginsTable(plugins, writer) return nil } func pluginsTable(plugins []plugins.Plugin, output io.Writer) error { writer := tabwriter.NewWriter(output, 10, 4, 1, ' ', 0) for _, plugin := range plugins { fmt.Fprintf(writer, "%s\t%s\t%s\n", plugin.Name, plugin.URL, plugin.Ref) } return writer.Flush() } ================================================ FILE: internal/info/info_test.go ================================================ package info import ( "os" "path/filepath" "strings" "testing" "github.com/asdf-vm/asdf/internal/config" "github.com/stretchr/testify/assert" ) func TestWrite(t *testing.T) { testDataDir := t.TempDir() err := os.MkdirAll(filepath.Join(testDataDir, "plugins"), 0o777) assert.Nil(t, err) conf := config.Config{DataDir: testDataDir} var stdout strings.Builder err = Write(conf, "0.15.0", &stdout) assert.Nil(t, err) output := stdout.String() // Simple format assertions assert.True(t, strings.Contains(output, "OS:\n")) assert.True(t, strings.Contains(output, "BASH VERSION:\n")) assert.True(t, strings.Contains(output, "SHELL:\n")) assert.True(t, strings.Contains(output, "ASDF VERSION:\n")) assert.True(t, strings.Contains(output, "INTERNAL VARIABLES:\n")) assert.True(t, strings.Contains(output, "ASDF INSTALLED PLUGINS:\n")) } ================================================ FILE: internal/installs/installs.go ================================================ // Package installs contains tool installation logic. It is "dumb" when it comes // to versions and treats versions as opaque strings. It cannot depend on the // versions package because the versions package relies on this page. package installs import ( "io/fs" "os" "path/filepath" "github.com/asdf-vm/asdf/internal/config" "github.com/asdf-vm/asdf/internal/data" "github.com/asdf-vm/asdf/internal/plugins" "github.com/asdf-vm/asdf/internal/toolversions" ) // Installed returns a slice of all installed versions for a given plugin func Installed(conf config.Config, plugin plugins.Plugin) (versions []string, err error) { installDirectory := data.InstallDirectory(conf.DataDir, plugin.Name) files, err := os.ReadDir(installDirectory) if err != nil { if _, ok := err.(*fs.PathError); ok { return versions, nil } return versions, err } for _, file := range files { if !file.IsDir() { continue } versions = append(versions, toolversions.VersionStringFromFSFormat(file.Name())) } return versions, err } // InstallPath returns the path to a tool installation func InstallPath(conf config.Config, plugin plugins.Plugin, version toolversions.Version) string { if version.Type == "path" { return version.Value } return filepath.Join(data.InstallDirectory(conf.DataDir, plugin.Name), toolversions.FormatForFS(version)) } // DownloadPath returns the download path for a particular plugin and version func DownloadPath(conf config.Config, plugin plugins.Plugin, version toolversions.Version) string { if version.Type == "path" { return "" } return filepath.Join(data.DownloadDirectory(conf.DataDir, plugin.Name), toolversions.FormatForFS(version)) } // IsInstalled checks if a specific version of a tool is installed func IsInstalled(conf config.Config, plugin plugins.Plugin, version toolversions.Version) bool { installDir := InstallPath(conf, plugin, version) // Check if version already installed _, err := os.Stat(installDir) return !os.IsNotExist(err) } ================================================ FILE: internal/installs/installs_test.go ================================================ package installs import ( "os" "path/filepath" "testing" "github.com/asdf-vm/asdf/internal/config" "github.com/asdf-vm/asdf/internal/installtest" "github.com/asdf-vm/asdf/internal/plugins" "github.com/asdf-vm/asdf/internal/repotest" "github.com/asdf-vm/asdf/internal/toolversions" "github.com/stretchr/testify/assert" ) const testPluginName = "lua" func TestDownloadPath(t *testing.T) { conf, plugin := generateConfig(t) t.Run("returns empty string when given path version", func(t *testing.T) { version := toolversions.Version{Type: "path", Value: "foo/bar"} path := DownloadPath(conf, plugin, version) assert.Empty(t, path) }) t.Run("returns empty string when given path version", func(t *testing.T) { version := toolversions.Version{Type: "version", Value: "1.2.3"} path := DownloadPath(conf, plugin, version) assert.Equal(t, path, filepath.Join(conf.DataDir, "downloads", "lua", "1.2.3")) }) } func TestInstallPath(t *testing.T) { conf, plugin := generateConfig(t) t.Run("returns empty string when given path version", func(t *testing.T) { version := toolversions.Version{Type: "path", Value: "foo/bar"} path := InstallPath(conf, plugin, version) assert.Equal(t, path, "foo/bar") }) t.Run("returns install path when given regular version as version", func(t *testing.T) { version := toolversions.Version{Type: "version", Value: "1.2.3"} path := InstallPath(conf, plugin, version) assert.Equal(t, path, filepath.Join(conf.DataDir, "installs", "lua", "1.2.3")) }) } func TestInstalled(t *testing.T) { conf, plugin := generateConfig(t) t.Run("returns empty slice for newly installed plugin", func(t *testing.T) { installedVersions, err := Installed(conf, plugin) assert.Nil(t, err) assert.Empty(t, installedVersions) }) t.Run("returns slice of all installed versions for a tool", func(t *testing.T) { mockInstall(t, conf, plugin, "1.0.0") installedVersions, err := Installed(conf, plugin) assert.Nil(t, err) assert.Equal(t, installedVersions, []string{"1.0.0"}) }) } func TestIsInstalled(t *testing.T) { conf, plugin := generateConfig(t) installVersion(t, conf, plugin, "1.0.0") t.Run("returns false when not installed", func(t *testing.T) { version := toolversions.Version{Type: "version", Value: "4.0.0"} assert.False(t, IsInstalled(conf, plugin, version)) }) t.Run("returns true when installed", func(t *testing.T) { version := toolversions.Version{Type: "version", Value: "1.0.0"} assert.True(t, IsInstalled(conf, plugin, version)) }) } // helper functions func generateConfig(t *testing.T) (config.Config, plugins.Plugin) { t.Helper() testDataDir := t.TempDir() conf, err := config.LoadConfig() assert.Nil(t, err) conf.DataDir = testDataDir _, err = repotest.InstallPlugin("dummy_plugin", testDataDir, testPluginName) assert.Nil(t, err) return conf, plugins.New(conf, testPluginName) } func mockInstall(t *testing.T, conf config.Config, plugin plugins.Plugin, versionStr string) { t.Helper() version := toolversions.Version{Type: "version", Value: versionStr} path := InstallPath(conf, plugin, version) err := os.MkdirAll(path, os.ModePerm) assert.Nil(t, err) } func installVersion(t *testing.T, conf config.Config, plugin plugins.Plugin, version string) { t.Helper() err := installtest.InstallOneVersion(conf, plugin, "version", version) assert.Nil(t, err) } ================================================ FILE: internal/installtest/installtest.go ================================================ // Package installtest provides functions used by various asdf tests for // installing versions of tools. It provides a simplified version of the // versions.InstallOneVersion function. package installtest import ( "fmt" "os" "path/filepath" "strings" "github.com/asdf-vm/asdf/internal/config" "github.com/asdf-vm/asdf/internal/plugins" "github.com/asdf-vm/asdf/internal/toolversions" ) const ( dataDirInstalls = "installs" dataDirDownloads = "downloads" ) // InstallOneVersion is a simplified version of versions.InstallOneVersion // function for use in Go tests. func InstallOneVersion(conf config.Config, plugin plugins.Plugin, versionType, version string) error { var stdOut strings.Builder var stdErr strings.Builder err := plugin.Exists() if err != nil { return err } downloadDir := DownloadPath(conf, plugin, version) installDir := InstallPath(conf, plugin, version) env := map[string]string{ "ASDF_INSTALL_TYPE": versionType, "ASDF_INSTALL_VERSION": version, "ASDF_INSTALL_PATH": installDir, "ASDF_DOWNLOAD_PATH": downloadDir, "ASDF_CONCURRENCY": "1", } err = os.MkdirAll(downloadDir, 0o777) if err != nil { return fmt.Errorf("unable to create download dir: %w", err) } err = plugin.RunCallback("download", []string{}, env, &stdOut, &stdErr) if _, ok := err.(plugins.NoCallbackError); err != nil && !ok { return fmt.Errorf("failed to run download callback: %w", err) } err = os.MkdirAll(installDir, 0o777) if err != nil { return fmt.Errorf("unable to create install dir: %w", err) } err = plugin.RunCallback("install", []string{}, env, &stdOut, &stdErr) if err != nil { return fmt.Errorf("failed to run install callback: %w", err) } return nil } // InstallPath returns the path to a tool installation func InstallPath(conf config.Config, plugin plugins.Plugin, version string) string { return filepath.Join(pluginInstallPath(conf, plugin), formatVersionStringForFS(version)) } // DownloadPath returns the download path for a particular plugin and version func DownloadPath(conf config.Config, plugin plugins.Plugin, version string) string { return filepath.Join(conf.DataDir, dataDirDownloads, plugin.Name, formatVersionStringForFS(version)) } func pluginInstallPath(conf config.Config, plugin plugins.Plugin) string { return filepath.Join(conf.DataDir, dataDirInstalls, plugin.Name) } func formatVersionStringForFS(version string) string { return toolversions.FormatForFS(toolversions.Parse(version)) } ================================================ FILE: internal/paths/paths.go ================================================ // Package paths contains a variety of helper functions responsible for // computing paths to various things. This package should not depend on any // other asdf packages. package paths import ( "strings" ) // RemoveFromPath returns the PATH without asdf shims path func RemoveFromPath(currentPath, pathToRemove string) string { var newPaths []string for _, fspath := range strings.Split(currentPath, ":") { if fspath != pathToRemove { newPaths = append(newPaths, fspath) } } return strings.Join(newPaths, ":") } ================================================ FILE: internal/paths/paths_test.go ================================================ package paths import ( "testing" "github.com/stretchr/testify/assert" ) func TestRemoveFromPath(t *testing.T) { t.Run("returns PATH string with matching path removed", func(t *testing.T) { got := RemoveFromPath("/foo/bar:/baz/bim:/home/user/bin", "/baz/bim") assert.Equal(t, got, "/foo/bar:/home/user/bin") }) t.Run("returns PATH string with multiple matching paths removed", func(t *testing.T) { got := RemoveFromPath("/foo/bar:/baz/bim:/baz/bim:/home/user/bin", "/baz/bim") assert.Equal(t, got, "/foo/bar:/home/user/bin") }) t.Run("returns PATH string unchanged when no matching path found", func(t *testing.T) { got := RemoveFromPath("/foo/bar:/baz/bim:/home/user/bin", "/path-not-present/") assert.Equal(t, got, "/foo/bar:/baz/bim:/home/user/bin") }) } ================================================ FILE: internal/pluginindex/pluginindex.go ================================================ // Package pluginindex is a package that handles fetching plugin repo URLs by // name for user convenience. package pluginindex import ( "fmt" "io/fs" "os" "path/filepath" "time" "github.com/asdf-vm/asdf/internal/git" "gopkg.in/ini.v1" ) const ( pluginIndexDir = "plugin-index" repoUpdatedFilename = "repo-updated" ) // PluginIndex is a struct representing the user's preferences for plugin index // and the plugin index on disk. type PluginIndex struct { repo git.Repoer directory string url string disableUpdate bool updateDurationMinutes int } // Plugin represents a plugin listed on a plugin index. type Plugin struct { Name string URL string } // Build returns a complete PluginIndex struct with default values set func Build(dataDir string, URL string, disableUpdate bool, updateDurationMinutes int) PluginIndex { directory := filepath.Join(dataDir, pluginIndexDir) return New(directory, URL, disableUpdate, updateDurationMinutes, &git.Repo{Directory: directory}) } // New initializes a new PluginIndex instance with the options passed in. func New(directory, url string, disableUpdate bool, updateDurationMinutes int, repo git.Repoer) PluginIndex { return PluginIndex{ repo: repo, directory: directory, url: url, disableUpdate: disableUpdate, updateDurationMinutes: updateDurationMinutes, } } // Get returns a slice of all available plugins func (p PluginIndex) Get() (plugins []Plugin, err error) { _, err = p.Refresh() if err != nil { return plugins, err } return getPlugins(p.directory) } // Refresh may update the plugin repo if it hasn't been updated in longer // than updateDurationMinutes. If the plugin repo needs to be updated the // repo will be invoked to perform the actual Git pull. func (p PluginIndex) Refresh() (bool, error) { err := os.MkdirAll(p.directory, os.ModePerm) if err != nil { return false, err } files, err := os.ReadDir(p.directory) if err != nil { return false, err } if len(files) == 0 { // directory empty, clone down repo err := p.repo.Clone(p.url, "") if err != nil { return false, fmt.Errorf("unable to initialize index: %w", err) } return touchFS(p.directory) } // directory must not be empty, repo must be present, maybe update updated, err := lastUpdated(p.directory) if err != nil { return p.doUpdate() } // Convert minutes to nanoseconds updateDurationNs := int64(p.updateDurationMinutes) * (6e10) if updated > updateDurationNs && !p.disableUpdate { return p.doUpdate() } return false, nil } func (p PluginIndex) doUpdate() (bool, error) { // pass in empty string as we want the repo to figure out what the latest // commit is _, _, _, err := p.repo.Update("") if err != nil { return false, fmt.Errorf("unable to update plugin index: %w", err) } // Touch update file return touchFS(p.directory) } // GetPluginSourceURL looks up a plugin by name and returns the repository URL // for easy install by the user. func (p PluginIndex) GetPluginSourceURL(name string) (string, error) { _, err := p.Refresh() if err != nil { return "", err } url, err := readPlugin(p.directory, name) if err != nil { return "", err } return url, nil } func touchFS(directory string) (bool, error) { filename := filepath.Join(directory, repoUpdatedFilename) file, err := os.OpenFile(filename, os.O_RDONLY|os.O_CREATE, 0o666) if err != nil { return false, fmt.Errorf("unable to create file plugin index touch file: %w", err) } file.Close() return true, nil } func lastUpdated(dir string) (int64, error) { info, err := os.Stat(filepath.Join(dir, repoUpdatedFilename)) if err != nil { return 0, fmt.Errorf("unable to read last updated file: %w", err) } // info.Atime_ns now contains the last access time updated := time.Now().UnixNano() - info.ModTime().UnixNano() return updated, nil } func readPlugin(dir, name string) (string, error) { filename := filepath.Join(dir, "plugins", name) pluginInfo, err := ini.Load(filename) if err != nil { return "", fmt.Errorf("plugin %s not found in repository", name) } return pluginInfo.Section("").Key("repository").String(), nil } func getPlugins(dir string) (plugins []Plugin, err error) { files, err := os.ReadDir(filepath.Join(dir, "plugins")) if _, ok := err.(*fs.PathError); ok { return plugins, nil } for _, file := range files { if !file.IsDir() { url, err := readPlugin(dir, file.Name()) if err != nil { return plugins, err } plugins = append(plugins, Plugin{Name: file.Name(), URL: url}) } } return plugins, err } ================================================ FILE: internal/pluginindex/pluginindex_test.go ================================================ package pluginindex import ( "errors" "fmt" "os" "path/filepath" "testing" "time" "github.com/asdf-vm/asdf/internal/git" "github.com/asdf-vm/asdf/internal/repotest" "github.com/stretchr/testify/assert" ) const ( mockIndexURL = "https://github.com/asdf-vm/asdf-plugins.git" badIndexURL = "http://asdf-vm.com/non-existent" fooPluginURL = "http://example.com/foo" elixirPluginURL = "https://github.com/asdf-vm/asdf-elixir.git" erlangPluginURL = "https://github.com/asdf-vm/asdf-erlang.git" ) type MockIndex struct { Directory string URL string } // Only defined so MockIndex complies with git.Repoer interface. These are not // used by pluginindex package code func (m *MockIndex) Head() (string, error) { return "", nil } func (m *MockIndex) RemoteURL() (string, error) { return "", nil } func (m *MockIndex) Clone(URL, _ string) error { m.URL = URL if m.URL == badIndexURL { return errors.New("unable to clone: repository not found") } err := writeMockPluginFile(m.Directory, "elixir", elixirPluginURL) if err != nil { return err } return nil } func (m *MockIndex) Update(_ string) (string, string, string, error) { if m.URL == badIndexURL { return "", "", "", errors.New("unable to clone: repository not found") } // Write another plugin file to mimic update err := writeMockPluginFile(m.Directory, "erlang", erlangPluginURL) if err != nil { return "", "", "", err } return "", "", "", nil } func writeMockPluginFile(dir, pluginName, pluginURL string) error { dirname := filepath.Join(dir, "plugins") err := os.MkdirAll(dirname, os.ModePerm) if err != nil { return err } filename := filepath.Join(dirname, pluginName) file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, os.ModePerm) if err != nil { return err } defer file.Close() _, err = file.WriteString(fmt.Sprintf("repository = %s", pluginURL)) if err != nil { return err } return nil } func TestGet(t *testing.T) { t.Run("returns populated slice of plugins when plugins exist in directory", func(t *testing.T) { dir := t.TempDir() pluginIndex := New(dir, mockIndexURL, true, 0, &MockIndex{Directory: dir}) plugins, err := pluginIndex.Get() assert.Nil(t, err) assert.Equal(t, plugins, []Plugin{{Name: "elixir", URL: "https://github.com/asdf-vm/asdf-elixir.git"}}) }) } func TestGetPluginSourceURL(t *testing.T) { t.Run("with Git returns a plugin url when provided name of existing plugin", func(t *testing.T) { dir := t.TempDir() indexDir := filepath.Join(dir, "index") err := os.Mkdir(indexDir, 0o777) assert.Nil(t, err) repoPath, err := repotest.GeneratePluginIndex(dir) assert.Nil(t, err) pluginIndex := New(indexDir, repoPath, true, 0, &git.Repo{Directory: indexDir}) url, err := pluginIndex.GetPluginSourceURL("foo") assert.Nil(t, err) assert.Equal(t, url, fooPluginURL) }) t.Run("returns a plugin url when provided name of existing plugin", func(t *testing.T) { dir := t.TempDir() pluginIndex := New(dir, mockIndexURL, true, 0, &MockIndex{Directory: dir}) url, err := pluginIndex.GetPluginSourceURL("elixir") assert.Nil(t, err) assert.Equal(t, url, elixirPluginURL) }) t.Run("returns a plugin url when provided name of existing plugin when loading from cache", func(t *testing.T) { dir := t.TempDir() pluginIndex := New(dir, mockIndexURL, false, 10, &MockIndex{Directory: dir}) url, err := pluginIndex.GetPluginSourceURL("elixir") assert.Nil(t, err) assert.Equal(t, url, elixirPluginURL) url, err = pluginIndex.GetPluginSourceURL("elixir") assert.Nil(t, err) assert.Equal(t, url, elixirPluginURL) }) t.Run("returns an error when given a name that isn't in the index", func(t *testing.T) { dir := t.TempDir() pluginIndex := New(dir, mockIndexURL, false, 10, &MockIndex{Directory: dir}) url, err := pluginIndex.GetPluginSourceURL("foobar") assert.EqualError(t, err, "plugin foobar not found in repository") assert.Equal(t, url, "") }) t.Run("returns an error when plugin index cannot be updated", func(t *testing.T) { dir := t.TempDir() // create plain text file so it appears plugin index already exists on disk file, err := os.OpenFile(filepath.Join(dir, "test"), os.O_RDONLY|os.O_CREATE, 0o666) assert.Nil(t, err) file.Close() repo := MockIndex{Directory: dir, URL: badIndexURL} pluginIndex := New(dir, badIndexURL, false, 10, &repo) url, err := pluginIndex.GetPluginSourceURL("lua") assert.EqualError(t, err, "unable to update plugin index: unable to clone: repository not found") assert.Equal(t, url, "") }) t.Run("returns error when given non-existent plugin index", func(t *testing.T) { dir := t.TempDir() pluginIndex := New(dir, badIndexURL, false, 10, &MockIndex{Directory: dir}) url, err := pluginIndex.GetPluginSourceURL("lua") assert.EqualError(t, err, "unable to initialize index: unable to clone: repository not found") assert.Equal(t, url, "") }) } func TestRefresh(t *testing.T) { t.Run("with Git updates repo when called once", func(t *testing.T) { dir := t.TempDir() indexDir := filepath.Join(dir, "index") err := os.Mkdir(indexDir, 0o777) assert.Nil(t, err) repoPath, err := repotest.GeneratePluginIndex(dir) assert.Nil(t, err) pluginIndex := New(indexDir, repoPath, false, 0, &git.Repo{Directory: indexDir}) url, err := pluginIndex.GetPluginSourceURL("foo") assert.Nil(t, err) assert.Equal(t, url, fooPluginURL) updated, err := pluginIndex.Refresh() assert.Nil(t, err) assert.True(t, updated) }) t.Run("updates repo when called once", func(t *testing.T) { dir := t.TempDir() pluginIndex := New(dir, mockIndexURL, false, 0, &MockIndex{Directory: dir}) updated, err := pluginIndex.Refresh() assert.Nil(t, err) assert.True(t, updated) url, err := pluginIndex.GetPluginSourceURL("erlang") assert.Nil(t, err) assert.Equal(t, url, erlangPluginURL) }) t.Run("does not update index when time has not elaspsed", func(t *testing.T) { dir := t.TempDir() pluginIndex := New(dir, mockIndexURL, false, 10, &MockIndex{Directory: dir}) // Call Refresh twice, the second call should not perform an update updated, err := pluginIndex.Refresh() assert.Nil(t, err) assert.True(t, updated) updated, err = pluginIndex.Refresh() assert.Nil(t, err) assert.False(t, updated) }) t.Run("updates plugin index when time has elaspsed", func(t *testing.T) { dir := t.TempDir() pluginIndex := New(dir, mockIndexURL, false, 0, &MockIndex{Directory: dir}) // Call Refresh twice, the second call should perform an update updated, err := pluginIndex.Refresh() assert.Nil(t, err) assert.True(t, updated) time.Sleep(10 * time.Nanosecond) updated, err = pluginIndex.Refresh() assert.Nil(t, err) assert.True(t, updated) }) t.Run("returns error when plugin index repo doesn't exist", func(t *testing.T) { dir := t.TempDir() pluginIndex := New(dir, badIndexURL, false, 0, &MockIndex{Directory: dir}) updated, err := pluginIndex.Refresh() assert.EqualError(t, err, "unable to initialize index: unable to clone: repository not found") assert.False(t, updated) }) } ================================================ FILE: internal/plugins/plugins.go ================================================ // Package plugins provides functions for interacting with asdf plugins package plugins import ( "errors" "fmt" "io" "io/fs" "os" "path/filepath" "regexp" "strings" "github.com/asdf-vm/asdf/internal/config" "github.com/asdf-vm/asdf/internal/data" "github.com/asdf-vm/asdf/internal/execute" "github.com/asdf-vm/asdf/internal/git" "github.com/asdf-vm/asdf/internal/hook" "github.com/asdf-vm/asdf/internal/pluginindex" ) // NewPluginAlreadyExists generates a new PluginAlreadyExists error instance for // a particular plugin func NewPluginAlreadyExists(plugin string) PluginAlreadyExists { return PluginAlreadyExists{plugin: plugin} } // PluginAlreadyExists is an error returned when the specified plugin already // exists type PluginAlreadyExists struct { plugin string } func (e PluginAlreadyExists) Error() string { return fmt.Sprintf(pluginAlreadyExistsMsg, e.plugin) } // PluginMissing is the error returned when Plugin.Exists is call and the plugin // doesn't exist on disk. type PluginMissing struct { plugin string } func (e PluginMissing) Error() string { return fmt.Sprintf(pluginMissingMsg, e.plugin) } // NoCallbackError is an error returned by RunCallback when a callback with // particular name does not exist type NoCallbackError struct { callback string plugin string } func (e NoCallbackError) Error() string { return fmt.Sprintf(hasNoCallbackMsg, e.plugin, e.callback) } // NoShimTemplateError is an error returned by ShimTemplatePath when an shim // template was not found in the plugin shims directory, or the file is not executable type NoShimTemplateError struct { shimName string plugin string } func (e NoShimTemplateError) Error() string { return fmt.Sprintf(hasNoCallbackMsg, e.plugin, e.shimName) } // NoCommandError is an error returned by ExtensionCommandPath when an extension // command with the given name does not exist type NoCommandError struct { command string plugin string } func (e NoCommandError) Error() string { return fmt.Sprintf(hasNoCommandMsg, e.plugin, e.command) } const ( dataDirPlugins = "plugins" invalidPluginNameMsg = "%s is invalid. Name may only contain lowercase letters, numbers, '_', and '-'" pluginAlreadyExistsMsg = "Plugin named %s already added" pluginMissingMsg = "Plugin named %s not installed" hasNoCallbackMsg = "Plugin named %s does not have a callback named %s" hasNoShimTemplateMsg = "Plugin named %s does not have a shim template named %s" hasNoCommandMsg = "Plugin named %s does not have a extension command named %s" ) // Plugin struct represents an asdf plugin to all asdf code. The name and dir // fields are the most used fields. Ref and Dir only still git info, which is // only information and shown to the user at times. type Plugin struct { Name string Dir string Ref string URL string } // New takes config and a plugin name and returns a Plugin struct. It is // intended for functions that need to quickly initialize a plugin. func New(config config.Config, name string) Plugin { pluginsDir := data.PluginDirectory(config.DataDir, name) return Plugin{Dir: pluginsDir, Name: name} } // LegacyFilenames returns a slice of filenames if the plugin contains the // list-legacy-filenames callback. func (p Plugin) LegacyFilenames() (filenames []string, err error) { var stdOut strings.Builder var stdErr strings.Builder err = p.RunCallback("list-legacy-filenames", []string{}, map[string]string{}, &stdOut, &stdErr) if err != nil { _, ok := err.(NoCallbackError) if ok { return []string{}, nil } return []string{}, err } for _, filename := range strings.Split(stdOut.String(), " ") { filenames = append(filenames, strings.TrimSpace(filename)) } return filenames, nil } // ParseLegacyVersionFile takes a file and uses the parse-legacy-file callback // script to parse it if the script is present. Otherwise just reads the file // directly. In either case the returned string is split on spaces and a slice // of versions is returned. func (p Plugin) ParseLegacyVersionFile(path string) (versions []string, err error) { parseLegacyFileName := "parse-legacy-file" parseCallbackPath := filepath.Join(p.Dir, "bin", parseLegacyFileName) var rawVersions string if _, err := os.Stat(parseCallbackPath); err == nil { var stdOut strings.Builder var stdErr strings.Builder err = p.RunCallback(parseLegacyFileName, []string{path}, map[string]string{}, &stdOut, &stdErr) if err != nil { return versions, err } rawVersions = stdOut.String() } else { bytes, err := os.ReadFile(path) if err != nil { return versions, err } rawVersions = string(bytes) } for _, version := range strings.Split(rawVersions, " ") { versions = append(versions, strings.TrimSpace(version)) } return versions, err } // Exists returns a boolean indicating whether or not the plugin exists on disk. func (p Plugin) Exists() error { exists, err := directoryExists(p.Dir) if err != nil { return err } if !exists { return PluginMissing{plugin: p.Name} } return nil } // RunCallback invokes a callback with the given name if it exists for the plugin func (p Plugin) RunCallback(name string, arguments []string, environment map[string]string, stdOut io.Writer, errOut io.Writer) error { callback, err := p.CallbackPath(name) if err != nil { return err } cmd := execute.New(fmt.Sprintf("'%s'", callback), arguments) cmd.Env = environment cmd.Stdout = stdOut cmd.Stderr = errOut return cmd.Run() } // CallbackPath returns the full file path to a callback script func (p Plugin) CallbackPath(name string) (string, error) { path := filepath.Join(p.Dir, "bin", name) _, err := os.Stat(path) if errors.Is(err, os.ErrNotExist) { return "", NoCallbackError{callback: name, plugin: p.Name} } return path, nil } // ShimTemplatePath returns the full file path to a shim, if it exists func (p Plugin) ShimTemplatePath(shimName string) (string, error) { const executableMode = 0o111 path := filepath.Join(p.Dir, "shims", shimName) stat, err := os.Stat(path) if errors.Is(err, os.ErrNotExist) || stat.Mode()&executableMode == 0 { return "", NoShimTemplateError{shimName: shimName, plugin: p.Name} } return path, nil } // GetExtensionCommands returns a slice of strings representing all available // extension commands for the plugin. func (p Plugin) GetExtensionCommands() ([]string, error) { commands := []string{} files, err := os.ReadDir(filepath.Join(p.Dir, "lib/commands")) if _, ok := err.(*fs.PathError); ok { return commands, nil } if err != nil { return commands, err } for _, file := range files { if !file.IsDir() { name := file.Name() if name == "command" { commands = append(commands, "") } else { if strings.HasPrefix(name, "command-") { commands = append(commands, strings.TrimPrefix(name, "command-")) } } } } return commands, nil } // ExtensionCommandPath returns the path to the plugin's extension command // script matching the name if it exists. func (p Plugin) ExtensionCommandPath(name string) (string, error) { commandName := "command" if name != "" { commandName = fmt.Sprintf("command-%s", name) } path := filepath.Join(p.Dir, "lib", "commands", commandName) _, err := os.Stat(path) if errors.Is(err, os.ErrNotExist) { return "", NoCommandError{command: name, plugin: p.Name} } return path, nil } // Update a plugin to a specific ref, or if no ref provided update to latest func (p Plugin) Update(conf config.Config, ref string, out, errout io.Writer) (string, error) { err := p.Exists() if err != nil { return "", fmt.Errorf("no such plugin: %s", p.Name) } repo := git.NewRepo(p.Dir) hook.Run(conf, "pre_asdf_plugin_update", []string{p.Name}) hook.Run(conf, fmt.Sprintf("pre_asdf_plugin_update_%s", p.Name), []string{p.Name}) newRef, oldSHA, newSHA, err := repo.Update(ref) if err != nil { return newRef, err } env := map[string]string{ "ASDF_DATA_DIR": conf.DataDir, "ASDF_PLUGIN_PATH": p.Dir, "ASDF_PLUGIN_PREV_REF": oldSHA, "ASDF_PLUGIN_POST_REF": newSHA, } err = p.RunCallback("post-plugin-update", []string{}, env, out, errout) if _, ok := err.(NoCallbackError); err != nil && !ok { return newRef, err } hook.Run(conf, "post_asdf_plugin_update", []string{p.Name}) hook.Run(conf, fmt.Sprintf("post_asdf_plugin_update_%s", p.Name), []string{}) return newRef, nil } // List takes config and flags for what to return and builds a list of plugins // representing the currently installed plugins on the system. func List(config config.Config, urls, refs bool) (plugins []Plugin, err error) { pluginsDir := data.PluginsDirectory(config.DataDir) files, err := os.ReadDir(pluginsDir) if err != nil { if _, ok := err.(*fs.PathError); ok { return []Plugin{}, nil } return plugins, err } for _, file := range files { if file.IsDir() { if refs || urls { var url string var refString string location := filepath.Join(pluginsDir, file.Name()) repo := git.NewRepo(location) // TODO: Improve these error messages if err != nil { return plugins, err } if refs { refString, err = repo.Head() if err != nil { return plugins, err } } if urls { url, err = repo.RemoteURL() if err != nil { return plugins, err } } plugins = append(plugins, Plugin{ Name: file.Name(), Dir: location, URL: url, Ref: refString, }) } else { plugins = append(plugins, Plugin{ Name: file.Name(), Dir: filepath.Join(pluginsDir, file.Name()), }) } } } return plugins, nil } // Add takes plugin name and Git URL and installs the plugin if it isn't // already installed func Add(config config.Config, pluginName, pluginURL, ref string) error { err := validatePluginName(pluginName) if err != nil { return err } exists, err := PluginExists(config.DataDir, pluginName) if err != nil { return fmt.Errorf("unable to check if plugin already exists: %w", err) } if exists { return NewPluginAlreadyExists(pluginName) } plugin := New(config, pluginName) if pluginURL == "" { // Ignore error here as the default value is fine disablePluginIndex, _ := config.DisablePluginShortNameRepository() if disablePluginIndex { return fmt.Errorf("Short-name plugin repository is disabled") } lastCheckDuration := 0 // We don't care about errors here as we can use the default value checkDuration, _ := config.PluginRepositoryLastCheckDuration() if !checkDuration.Never { lastCheckDuration = checkDuration.Every } index := pluginindex.Build(config.DataDir, config.PluginIndexURL, false, lastCheckDuration) var err error pluginURL, err = index.GetPluginSourceURL(pluginName) if err != nil { return fmt.Errorf("error fetching plugin URL: %s", err) } } plugin.URL = pluginURL // Run pre hooks hook.Run(config, "pre_asdf_plugin_add", []string{plugin.Name}) hook.Run(config, fmt.Sprintf("pre_asdf_plugin_add_%s", plugin.Name), []string{}) err = git.NewRepo(plugin.Dir).Clone(plugin.URL, ref) if err != nil { return err } err = os.MkdirAll(data.DownloadDirectory(config.DataDir, plugin.Name), 0o777) if err != nil { return err } env := map[string]string{"ASDF_PLUGIN_SOURCE_URL": plugin.URL, "ASDF_PLUGIN_PATH": plugin.Dir} plugin.RunCallback("post-plugin-add", []string{}, env, os.Stdout, os.Stderr) // Run post hooks hook.Run(config, "post_asdf_plugin_add", []string{plugin.Name}) hook.Run(config, fmt.Sprintf("post_asdf_plugin_add_%s", plugin.Name), []string{}) return nil } // Remove uninstalls a plugin by removing it from the file system if installed func Remove(config config.Config, pluginName string, stdout, stderr io.Writer) error { err := validatePluginName(pluginName) if err != nil { return err } plugin := New(config, pluginName) exists, err := PluginExists(config.DataDir, pluginName) if err != nil { return fmt.Errorf("unable to check if plugin exists: %w", err) } if !exists { return fmt.Errorf("No such plugin: %s", pluginName) } hook.Run(config, "pre_asdf_plugin_remove", []string{plugin.Name}) hook.Run(config, fmt.Sprintf("pre_asdf_plugin_remove_%s", plugin.Name), []string{}) env := map[string]string{ "ASDF_PLUGIN_PATH": plugin.Dir, "ASDF_PLUGIN_SOURCE_URL": plugin.URL, } plugin.RunCallback("pre-plugin-remove", []string{}, env, stdout, stderr) pluginDir := data.PluginDirectory(config.DataDir, pluginName) downloadDir := data.DownloadDirectory(config.DataDir, pluginName) installDir := data.InstallDirectory(config.DataDir, pluginName) err = os.RemoveAll(downloadDir) err2 := os.RemoveAll(pluginDir) err3 := os.RemoveAll(installDir) if err != nil { return err } if err2 != nil { return err2 } hook.Run(config, "post_asdf_plugin_remove", []string{plugin.Name}) hook.Run(config, fmt.Sprintf("post_asdf_plugin_remove_%s", plugin.Name), []string{}) return err3 } // PluginExists returns a boolean indicating whether or not a plugin with the // provided name is currently installed func PluginExists(dataDir, pluginName string) (bool, error) { pluginDir := data.PluginDirectory(dataDir, pluginName) return directoryExists(pluginDir) } func directoryExists(dir string) (bool, error) { fileInfo, err := os.Stat(dir) if errors.Is(err, os.ErrNotExist) { return false, nil } if err != nil { return false, err } return fileInfo.IsDir(), nil } func validatePluginName(name string) error { match, err := regexp.MatchString("^[[:lower:][:digit:]_-]+$", name) if err != nil { return err } if !match { return fmt.Errorf(invalidPluginNameMsg, name) } return nil } ================================================ FILE: internal/plugins/plugins_test.go ================================================ package plugins import ( "fmt" "os" "path/filepath" "strings" "testing" "github.com/asdf-vm/asdf/internal/config" "github.com/asdf-vm/asdf/internal/data" "github.com/asdf-vm/asdf/internal/repotest" "github.com/stretchr/testify/assert" ) const testPluginName = "lua" func TestList(t *testing.T) { testDataDir := t.TempDir() conf := config.Config{DataDir: testDataDir} testRepo, err := repotest.GeneratePlugin("dummy_plugin", testDataDir, testPluginName) assert.Nil(t, err) err = Add(conf, testPluginName, testRepo, "") assert.Nil(t, err) t.Run("when urls and refs are set to false returns plugin names", func(t *testing.T) { plugins, err := List(conf, false, false) assert.Nil(t, err) plugin := plugins[0] assert.Equal(t, "lua", plugin.Name) assert.NotZero(t, plugin.Dir) assert.Zero(t, plugin.URL) assert.Zero(t, plugin.Ref) }) t.Run("when urls is set to true returns plugins with repo urls set", func(t *testing.T) { plugins, err := List(conf, true, false) assert.Nil(t, err) plugin := plugins[0] assert.Equal(t, "lua", plugin.Name) assert.NotZero(t, plugin.Dir) assert.Zero(t, plugin.Ref) assert.NotZero(t, plugin.URL) }) t.Run("when refs is set to true returns plugins with current repo refs set", func(t *testing.T) { plugins, err := List(conf, false, true) assert.Nil(t, err) plugin := plugins[0] assert.Equal(t, "lua", plugin.Name) assert.NotZero(t, plugin.Dir) assert.NotZero(t, plugin.Ref) assert.Zero(t, plugin.URL) }) t.Run("when refs and urls are both set to true returns plugins with both set", func(t *testing.T) { plugins, err := List(conf, true, true) assert.Nil(t, err) plugin := plugins[0] assert.Equal(t, "lua", plugin.Name) assert.NotZero(t, plugin.Dir) assert.NotZero(t, plugin.Ref) assert.NotZero(t, plugin.URL) }) } func TestNew(t *testing.T) { testDataDir := t.TempDir() conf := config.Config{DataDir: testDataDir} t.Run("returns Plugin struct with Dir and Name fields set correctly", func(t *testing.T) { plugin := New(conf, "test-plugin") assert.Equal(t, "test-plugin", plugin.Name) assert.Equal(t, filepath.Join(testDataDir, "plugins", "test-plugin"), plugin.Dir) }) } func TestAdd(t *testing.T) { testDataDir := t.TempDir() t.Run("when given an invalid plugin name prints an error", func(t *testing.T) { invalids := []string{"plugin^name", "plugin%name", "plugin name", "PLUGIN_NAME"} for _, invalid := range invalids { t.Run(invalid, func(t *testing.T) { err := Add(config.Config{}, invalid, "never-cloned", "") expectedErrMsg := "is invalid. Name may only contain lowercase letters, numbers, '_', and '-'" if !strings.Contains(err.Error(), expectedErrMsg) { t.Errorf("Expected an error with message %v", expectedErrMsg) } }) } }) t.Run("when plugin with same name already exists prints an error", func(t *testing.T) { conf := config.Config{DataDir: testDataDir} // Add plugin repoPath, err := repotest.GeneratePlugin("dummy_plugin", testDataDir, testPluginName) assert.Nil(t, err) err = Add(conf, testPluginName, repoPath, "") if err != nil { t.Fatal("Expected to be able to add plugin") } // Add it again to trigger error err = Add(conf, testPluginName, repoPath, "") if err == nil { t.Fatal("expected error got nil") } expectedErrMsg := "Plugin named lua already added" if !strings.Contains(err.Error(), expectedErrMsg) { t.Errorf("Expected an error with message %v", expectedErrMsg) } }) t.Run("when plugin name is valid but URL is invalid prints an error", func(t *testing.T) { t.Setenv("LANG", "C") // git error messages are locale dependent conf := config.Config{DataDir: testDataDir} err := Add(conf, "foo", "foobar", "") assert.ErrorContains(t, err, "unable to clone plugin: fatal: repository 'foobar' does not exist") }) t.Run("when plugin name and URL are valid installs plugin", func(t *testing.T) { testDataDir := t.TempDir() conf := config.Config{DataDir: testDataDir} pluginPath, err := repotest.GeneratePlugin("dummy_plugin", testDataDir, testPluginName) assert.Nil(t, err) err = Add(conf, testPluginName, pluginPath, "") assert.Nil(t, err, "Expected to be able to add plugin") // Assert plugin directory contains Git repo with bin directory pluginDir := data.PluginDirectory(testDataDir, testPluginName) _, err = os.ReadDir(pluginDir + "/.git") assert.Nil(t, err) entries, err := os.ReadDir(pluginDir + "/bin") assert.Nil(t, err) assert.Equal(t, 12, len(entries)) }) t.Run("when parameters are valid creates plugin download dir", func(t *testing.T) { testDataDir := t.TempDir() conf := config.Config{DataDir: testDataDir} repoPath, err := repotest.GeneratePlugin("dummy_plugin", testDataDir, testPluginName) assert.Nil(t, err) err = Add(conf, testPluginName, repoPath, "") assert.Nil(t, err) // Assert download dir exists downloadDir := data.DownloadDirectory(testDataDir, testPluginName) _, err = os.Stat(downloadDir) assert.Nil(t, err) }) } func TestRemove(t *testing.T) { testDataDir := t.TempDir() conf := config.Config{DataDir: testDataDir} repoPath, err := repotest.GeneratePlugin("dummy_plugin", testDataDir, testPluginName) assert.Nil(t, err) err = Add(conf, testPluginName, repoPath, "") assert.Nil(t, err) t.Run("returns error when plugin with name does not exist", func(t *testing.T) { var stdout strings.Builder var stderr strings.Builder err := Remove(conf, "nonexistent", &stdout, &stderr) assert.NotNil(t, err) assert.ErrorContains(t, err, "No such plugin") }) t.Run("returns error when invalid plugin name is given", func(t *testing.T) { var stdout strings.Builder var stderr strings.Builder err := Remove(conf, "foo/bar/baz", &stdout, &stderr) assert.NotNil(t, err) expectedErrMsg := "is invalid. Name may only contain lowercase letters, numbers, '_', and '-'" assert.ErrorContains(t, err, expectedErrMsg) }) t.Run("removes plugin when passed name of installed plugin", func(t *testing.T) { var stdout strings.Builder var stderr strings.Builder err := Remove(conf, testPluginName, &stdout, &stderr) assert.Nil(t, err) pluginDir := data.PluginDirectory(testDataDir, testPluginName) _, err = os.Stat(pluginDir) assert.NotNil(t, err) assert.True(t, os.IsNotExist(err)) }) t.Run("removes plugin download dir when passed name of installed plugin", func(t *testing.T) { var stdout strings.Builder var stderr strings.Builder err := Add(conf, testPluginName, repoPath, "") assert.Nil(t, err) err = Remove(conf, testPluginName, &stdout, &stderr) assert.Nil(t, err) downloadDir := data.DownloadDirectory(testDataDir, testPluginName) _, err = os.Stat(downloadDir) assert.NotNil(t, err) assert.True(t, os.IsNotExist(err)) }) } func TestUpdate(t *testing.T) { testDataDir := t.TempDir() conf := config.Config{DataDir: testDataDir} repoPath, err := repotest.GeneratePlugin("dummy_plugin", testDataDir, testPluginName) assert.Nil(t, err) assert.Nil(t, Add(conf, testPluginName, repoPath, "")) noPostUpdateCallbackPlugin := "no-post-update" repoPath, err = repotest.GeneratePlugin("dummy_plugin", testDataDir, noPostUpdateCallbackPlugin) assert.Nil(t, err) assert.Nil(t, os.Remove(filepath.Join(repoPath, "bin", "post-plugin-update"))) assert.Nil(t, Add(conf, noPostUpdateCallbackPlugin, repoPath, "")) badPluginName := "badplugin" badRepo := data.PluginDirectory(testDataDir, badPluginName) err = os.MkdirAll(badRepo, 0o777) assert.Nil(t, err) tests := []struct { desc string givenConf config.Config givenName string givenRef string wantSomeRef bool wantErrMsg string }{ { desc: "returns error when plugin with name does not exist", givenConf: conf, givenName: "nonexistent", givenRef: "", wantSomeRef: false, wantErrMsg: "no such plugin: nonexistent", }, { desc: "returns error when plugin repo does not exist", givenConf: conf, givenName: "badplugin", givenRef: "", wantSomeRef: false, wantErrMsg: "not a git repository", }, { desc: "updates plugin when plugin with name exists", givenConf: conf, givenName: testPluginName, givenRef: "", wantSomeRef: true, wantErrMsg: "", }, { desc: "updates plugin when plugin when post-plugin-update callback does not exist", givenConf: conf, givenName: noPostUpdateCallbackPlugin, givenRef: "", wantSomeRef: true, wantErrMsg: "", }, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { var blackhole strings.Builder plugin := New(conf, tt.givenName) updatedToRef, err := plugin.Update(tt.givenConf, tt.givenRef, &blackhole, &blackhole) if tt.wantErrMsg == "" { assert.Nil(t, err) } else { assert.NotNil(t, err) assert.ErrorContains(t, err, tt.wantErrMsg) } if tt.wantSomeRef == true { assert.NotZero(t, updatedToRef) } else { assert.Zero(t, updatedToRef) } }) } } func TestExists(t *testing.T) { testDataDir := t.TempDir() conf := config.Config{DataDir: testDataDir} _, err := repotest.InstallPlugin("dummy_plugin", testDataDir, testPluginName) assert.Nil(t, err) existingPlugin := New(conf, testPluginName) t.Run("returns nil if plugin exists", func(t *testing.T) { err := existingPlugin.Exists() assert.Nil(t, err) }) t.Run("returns PluginMissing error when plugin missing", func(t *testing.T) { missingPlugin := New(conf, "non-existent") err := missingPlugin.Exists() assert.Equal(t, err, PluginMissing{plugin: "non-existent"}) }) } func TestPluginExists(t *testing.T) { testDataDir := t.TempDir() pluginDir := data.PluginDirectory(testDataDir, testPluginName) err := os.MkdirAll(pluginDir, 0o777) if err != nil { t.Errorf("got %v, expected nil", err) } t.Run("returns true when plugin exists", func(t *testing.T) { exists, err := PluginExists(testDataDir, testPluginName) if err != nil { t.Errorf("got %v, expected nil", err) } if exists != true { t.Error("got false, expected true") } }) t.Run("returns false when plugin path is file and not dir", func(t *testing.T) { pluginName := "file" pluginDir := data.PluginDirectory(testDataDir, pluginName) err := touchFile(pluginDir) if err != nil { t.Errorf("got %v, expected nil", err) } exists, err := PluginExists(testDataDir, pluginName) if err != nil { t.Errorf("got %v, expected nil", err) } if exists != false { t.Error("got false, expected true") } }) t.Run("returns false when plugin dir does not exist", func(t *testing.T) { exists, err := PluginExists(testDataDir, "non-existent") if err != nil { t.Errorf("got %v, expected nil", err) } if exists != false { t.Error("got false, expected true") } }) } func TestValidatePluginName(t *testing.T) { t.Run("returns no error when plugin name is valid", func(t *testing.T) { err := validatePluginName(testPluginName) assert.Nil(t, err) }) invalids := []string{"plugin^name", "plugin%name", "plugin name", "PLUGIN_NAME"} for _, invalid := range invalids { t.Run(invalid, func(t *testing.T) { err := validatePluginName(invalid) if err == nil { t.Error("Expected an error") } }) } } func TestRunCallback(t *testing.T) { emptyEnv := map[string]string{} testDataDir := t.TempDir() conf := config.Config{DataDir: testDataDir} _, err := repotest.InstallPlugin("dummy_plugin", testDataDir, testPluginName) assert.Nil(t, err) plugin := New(conf, testPluginName) t.Run("returns NoCallback error when callback with name not found", func(t *testing.T) { var stdout strings.Builder var stderr strings.Builder err = plugin.RunCallback("non-existent", []string{}, emptyEnv, &stdout, &stderr) assert.Equal(t, err.(NoCallbackError).Error(), "Plugin named lua does not have a callback named non-existent") }) t.Run("passes argument to command", func(t *testing.T) { var stdout strings.Builder var stderr strings.Builder err = plugin.RunCallback("debug", []string{"123"}, emptyEnv, &stdout, &stderr) assert.Nil(t, err) assert.Equal(t, "123\n", stdout.String()) assert.Equal(t, "", stderr.String()) }) t.Run("passes arguments to command", func(t *testing.T) { var stdout strings.Builder var stderr strings.Builder err = plugin.RunCallback("debug", []string{"123", "test string"}, emptyEnv, &stdout, &stderr) assert.Nil(t, err) assert.Equal(t, "123 test string\n", stdout.String()) assert.Equal(t, "", stderr.String()) }) t.Run("passes env to command", func(t *testing.T) { var stdout strings.Builder var stderr strings.Builder err = plugin.RunCallback("post-plugin-update", []string{}, map[string]string{"ASDF_PLUGIN_PREV_REF": "TEST"}, &stdout, &stderr) assert.Nil(t, err) assert.Equal(t, "plugin updated path= old git-ref=TEST new git-ref=\n", stdout.String()) assert.Equal(t, "", stderr.String()) }) } func TestCallbackPath(t *testing.T) { testDataDir := t.TempDir() conf := config.Config{DataDir: testDataDir} _, err := repotest.InstallPlugin("dummy_plugin", testDataDir, testPluginName) assert.Nil(t, err) plugin := New(conf, testPluginName) t.Run("returns callback path when callback exists", func(t *testing.T) { path, err := plugin.CallbackPath("install") assert.Nil(t, err) assert.Equal(t, filepath.Base(path), "install") assert.Equal(t, filepath.Base(filepath.Dir(filepath.Dir(path))), plugin.Name) assert.Equal(t, filepath.Base(filepath.Dir(filepath.Dir(filepath.Dir(path)))), "plugins") }) t.Run("returns error when callback does not exist", func(t *testing.T) { path, err := plugin.CallbackPath("non-existent") assert.Equal(t, err.(NoCallbackError).Error(), "Plugin named lua does not have a callback named non-existent") assert.Equal(t, path, "") }) } func TestGetExtensionCommands(t *testing.T) { testDataDir := t.TempDir() conf := config.Config{DataDir: testDataDir} _, err := repotest.InstallPlugin("dummy_plugin", testDataDir, testPluginName) assert.Nil(t, err) plugin := New(conf, testPluginName) t.Run("returns empty slice when no extension commands defined", func(t *testing.T) { commands, err := plugin.GetExtensionCommands() assert.Nil(t, err) assert.Empty(t, commands) }) t.Run("returns slice of with default extension command if it is present", func(t *testing.T) { assert.Nil(t, writeExtensionCommand(t, plugin, "", "#!/usr/bin/env bash\necho $1")) commands, err := plugin.GetExtensionCommands() assert.Nil(t, err) assert.Equal(t, commands, []string{""}) }) t.Run("returns slice of all extension commands when they are present", func(t *testing.T) { assert.Nil(t, writeExtensionCommand(t, plugin, "", "#!/usr/bin/env bash\necho $1")) assert.Nil(t, writeExtensionCommand(t, plugin, "foobar", "#!/usr/bin/env bash\necho $1")) commands, err := plugin.GetExtensionCommands() assert.Nil(t, err) assert.Equal(t, commands, []string{"", "foobar"}) }) } func TestExtensionCommandPath(t *testing.T) { testDataDir := t.TempDir() conf := config.Config{DataDir: testDataDir} _, err := repotest.InstallPlugin("dummy_plugin", testDataDir, testPluginName) assert.Nil(t, err) plugin := New(conf, testPluginName) t.Run("returns NoCallback error when callback with name not found", func(t *testing.T) { path, err := plugin.ExtensionCommandPath("non-existent") assert.Equal(t, err.(NoCommandError).Error(), "Plugin named lua does not have a extension command named non-existent") assert.Equal(t, path, "") }) t.Run("returns default extension command script when no name", func(t *testing.T) { assert.Nil(t, writeExtensionCommand(t, plugin, "", "#!/usr/bin/env bash\necho $1")) path, err := plugin.ExtensionCommandPath("") assert.Nil(t, err) assert.Equal(t, filepath.Base(path), "command") }) t.Run("passes arguments to command", func(t *testing.T) { assert.Nil(t, writeExtensionCommand(t, plugin, "debug", "#!/usr/bin/env bash\necho $@")) path, err := plugin.ExtensionCommandPath("debug") assert.Nil(t, err) assert.Equal(t, filepath.Base(path), "command-debug") }) } func writeExtensionCommand(t *testing.T, plugin Plugin, name, contents string) error { t.Helper() assert.Nil(t, os.MkdirAll(filepath.Join(plugin.Dir, "lib", "commands"), 0o777)) filename := "command" if name != "" { filename = fmt.Sprintf("command-%s", name) } path := filepath.Join(plugin.Dir, "lib", "commands", filename) err := os.WriteFile(path, []byte(contents), 0o777) return err } func TestLegacyFilenames(t *testing.T) { testDataDir := t.TempDir() conf := config.Config{DataDir: testDataDir} _, err := repotest.InstallPlugin("dummy_plugin", testDataDir, testPluginName) assert.Nil(t, err) plugin := New(conf, testPluginName) t.Run("returns list of filenames when list-legacy-filenames callback is present", func(t *testing.T) { filenames, err := plugin.LegacyFilenames() assert.Nil(t, err) assert.Equal(t, filenames, []string{".dummy-version", ".dummyrc"}) }) t.Run("returns empty list when list-legacy-filenames callback not present", func(t *testing.T) { testPluginName := "foobar" _, err := repotest.InstallPlugin("dummy_plugin_no_download", testDataDir, testPluginName) assert.Nil(t, err) plugin := New(conf, testPluginName) filenames, err := plugin.LegacyFilenames() assert.Nil(t, err) assert.Equal(t, filenames, []string{}) }) } func TestParseLegacyVersionFile(t *testing.T) { testDataDir := t.TempDir() conf := config.Config{DataDir: testDataDir} _, err := repotest.InstallPlugin("dummy_plugin", testDataDir, testPluginName) assert.Nil(t, err) plugin := New(conf, testPluginName) data := []byte("dummy-1.2.3") currentDir := t.TempDir() path := filepath.Join(currentDir, ".dummy-version") err = os.WriteFile(path, data, 0o666) assert.Nil(t, err) t.Run("returns file contents unchanged when parse-legacy-file callback not present", func(t *testing.T) { testPluginName := "foobar" _, err := repotest.InstallPlugin("dummy_plugin_no_download", testDataDir, testPluginName) assert.Nil(t, err) plugin := New(conf, testPluginName) versions, err := plugin.ParseLegacyVersionFile(path) assert.Nil(t, err) assert.Equal(t, versions, []string{"dummy-1.2.3"}) }) t.Run("returns file contents parsed by parse-legacy-file callback when it is present", func(t *testing.T) { versions, err := plugin.ParseLegacyVersionFile(path) assert.Nil(t, err) assert.Equal(t, versions, []string{"1.2.3"}) }) t.Run("returns error when passed file that doesn't exist", func(t *testing.T) { versions, err := plugin.ParseLegacyVersionFile("non-existent-file") assert.Error(t, err) assert.Empty(t, versions) }) } func touchFile(name string) error { file, err := os.OpenFile(name, os.O_RDONLY|os.O_CREATE, 0o644) if err != nil { return err } return file.Close() } ================================================ FILE: internal/repotest/repotest.go ================================================ // Package repotest contains various test helpers for tests that work with code // relying on plugin Git repos and the asdf plugin index // // Three main actions: // // * Install plugin index repo into asdf (index contains records that point to // local plugins defined by this package) // * Install plugin into asdf data dir // * Create local plugin repo that can be cloned into asdf package repotest import ( "fmt" "os" "os/exec" "path/filepath" "strings" cp "github.com/otiai10/copy" ) const fixturesDir = "fixtures" // Setup copies all files into place and initializes all repos for any Go test // that needs either plugin repos or the plugin index repo. func Setup(asdfDataDir string) error { if err := InstallPluginIndex(asdfDataDir); err != nil { return err } return nil } // WritePluginCallback is for creating new plugin callbacks on the fly. func WritePluginCallback(pluginDir, callbackName, script string) error { return os.WriteFile(filepath.Join(pluginDir, "bin", callbackName), []byte(script), 0o777) } // InstallPlugin copies in the specified plugin fixture into the asdfDataDir's // plugin directory and initializes a Git repo for it so asdf treats it as // installed. func InstallPlugin(fixtureName, asdfDataDir, pluginName string) (string, error) { root, err := getModuleRoot() if err != nil { return "", err } destDir := filepath.Join(asdfDataDir, "plugins") return generatePluginInDir(root, fixtureName, destDir, pluginName) } // GeneratePlugin copies in the specified plugin fixture into a test directory // and initializes a Git repo for it so it can be installed by asdf. func GeneratePlugin(fixtureName, dir, pluginName string) (string, error) { root, err := getModuleRoot() if err != nil { return "", err } fixturesDir := filepath.Join(dir, fixturesDir) return generatePluginInDir(root, fixtureName, fixturesDir, pluginName) } // InstallPluginIndex generates and installs a plugin index Git repo inside of // the provided asdf data directory. func InstallPluginIndex(asdfDataDir string) error { root, err := getModuleRoot() if err != nil { return err } // Copy in plugin index source := filepath.Join(root, "test/fixtures/dummy_plugins_repo") return cp.Copy(source, filepath.Join(asdfDataDir, "plugin-index")) } // GeneratePluginIndex generates a mock plugin index Git repo inside the given // directory. func GeneratePluginIndex(asdfDataDir string) (string, error) { root, err := getModuleRoot() if err != nil { return "", err } // Copy in plugin index source := filepath.Join(root, "test/fixtures/dummy_plugins_repo") destination := filepath.Join(asdfDataDir, fixturesDir, "plugin-index") err = cp.Copy(source, destination) if err != nil { return destination, fmt.Errorf("unable to copy in plugin index: %w", err) } // Generate git repo for plugin return createGitRepo(destination) } func generatePluginInDir(root, fixtureName, outputDir, pluginName string) (string, error) { // Copy in plugin files into output dir pluginPath, err := copyInPlugin(root, fixtureName, outputDir, pluginName) if err != nil { return pluginPath, fmt.Errorf("unable to copy in plugin files: %w", err) } // Generate git repo for plugin return createGitRepo(pluginPath) } func getModuleRoot() (string, error) { cwd, err := os.Getwd() if err != nil { return "", fmt.Errorf("unable to get current working directory: %w", err) } root := findModuleRoot(cwd) return root, nil } func createGitRepo(location string) (string, error) { // Definitely some opportunities to refactor here. This code might be // simplified by switching to the Go git library err := runCmd("git", "-C", location, "init", "-q", "-b", "master") if err != nil { return location, err } err = runCmd("git", "-C", location, "config", "user.name", "\"Test\"") if err != nil { return location, err } err = runCmd("git", "-C", location, "config", "user.email", "\"test@example.com\"") if err != nil { return location, err } err = runCmd("git", "-C", location, "add", "-A") if err != nil { return location, err } err = runCmd("git", "-C", location, "commit", "-q", "-m", "init repo") if err != nil { return location, err } err = runCmd("touch", filepath.Join(location, "README.md")) if err != nil { return location, err } err = runCmd("git", "-C", location, "add", "-A") if err != nil { return location, err } err = runCmd("git", "-C", location, "commit", "-q", "-m", "add readme") if err != nil { return location, err } // kind of ugly but I want a remote with a valid path so I use the same // location as the remote. Probably should refactor err = runCmd("git", "-C", location, "remote", "add", "origin", location) if err != nil { return location, err } return location, err } func copyInPlugin(root, name, destination, newName string) (string, error) { source := filepath.Join(root, "test/fixtures/", name) dest := filepath.Join(destination, newName) return dest, cp.Copy(source, dest) } // Taken from https://github.com/golang/go/blob/9e3b1d53a012e98cfd02de2de8b1bd53522464d4/src/cmd/go/internal/modload/init.go#L1504C1-L1522C2 because that function is in an internal module // and I can't rely on it. func findModuleRoot(dir string) (roots string) { if dir == "" { panic("dir not set") } dir = filepath.Clean(dir) // Look for enclosing go.mod. for { if fi, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil && !fi.IsDir() { return dir } d := filepath.Dir(dir) if d == dir { break } dir = d } return "" } // helper function to make running commands easier func runCmd(cmdName string, args ...string) error { cmd := exec.Command(cmdName, args...) // Capture stdout and stderr var stdout strings.Builder var stderr strings.Builder cmd.Stdout = &stdout cmd.Stderr = &stderr // Global env vars // GIT_CONFIG_GLOBAL=/dev/null prevents git from looking for user settings like commit.gpgSign and user.name cmd.Env = []string{"GIT_CONFIG_GLOBAL=/dev/null"} err := cmd.Run() if err != nil { // If command fails print both stderr and stdout fmt.Println("stdout:", stdout.String()) fmt.Println("stderr:", stderr.String()) return err } return nil } ================================================ FILE: internal/resolve/resolve.go ================================================ // Package resolve contains functions for resolving a tool version in a given // directory. This is a core feature of asdf as asdf must be able to resolve a // tool version in any directory if set. package resolve import ( "fmt" "os" "path" "strings" "github.com/asdf-vm/asdf/internal/config" "github.com/asdf-vm/asdf/internal/plugins" "github.com/asdf-vm/asdf/internal/toolversions" ) // ToolVersions represents a tool along with versions specified for it type ToolVersions struct { Versions []string Directory string Source string } // Version takes a plugin and a directory and resolves the tool to one or more // versions. func Version(conf config.Config, plugin plugins.Plugin, directory string) (versions ToolVersions, found bool, err error) { version, envVariableName, found := findVersionsInEnv(plugin.Name) if found { return ToolVersions{Versions: version, Source: envVariableName}, true, nil } for !found { versions, found, err = findVersionsInDir(conf, plugin, directory) if err != nil { return versions, false, err } nextDir := path.Dir(directory) // If current dir and next dir are the same it means we've reached `/` and // have no more parent directories to search. if nextDir == directory { // If no version found, try current users home directory. I'd like to // eventually remove this feature. homeDir, osErr := os.UserHomeDir() if osErr != nil { break } versions, found, err = findVersionsInDir(conf, plugin, homeDir) break } directory = nextDir } return versions, found, err } func findVersionsInDir(conf config.Config, plugin plugins.Plugin, directory string) (versions ToolVersions, found bool, err error) { filepath := path.Join(directory, conf.DefaultToolVersionsFilename) if _, err = os.Stat(filepath); err == nil { versions, found, err := toolversions.FindToolVersions(filepath, plugin.Name) if found || err != nil { return ToolVersions{Versions: versions, Source: conf.DefaultToolVersionsFilename, Directory: directory}, found, err } } legacyFiles, err := conf.LegacyVersionFile() if err != nil { return versions, found, err } if legacyFiles { versions, found, err := findVersionsInLegacyFile(plugin, directory) if found || err != nil { return versions, found, err } } return versions, found, nil } // findVersionsInEnv returns the version from the environment if present func findVersionsInEnv(pluginName string) ([]string, string, bool) { envVariableName := variableVersionName(pluginName) versionString := os.Getenv(envVariableName) if versionString == "" { return []string{}, envVariableName, false } return parseVersion(versionString), envVariableName, true } // findVersionsInLegacyFile looks up a legacy version in the given directory if // the specified plugin has a list-legacy-filenames callback script. If the // callback script exists asdf will look for files with the given name in the // current and extract the version from them. func findVersionsInLegacyFile(plugin plugins.Plugin, directory string) (versions ToolVersions, found bool, err error) { var legacyFileNames []string legacyFileNames, err = plugin.LegacyFilenames() if err != nil { return versions, false, err } for _, filename := range legacyFileNames { filepath := path.Join(directory, filename) if _, err := os.Stat(filepath); err == nil { versionsSlice, err := plugin.ParseLegacyVersionFile(filepath) if len(versionsSlice) == 0 || (len(versionsSlice) == 1 && versionsSlice[0] == "") { return versions, false, nil } return ToolVersions{Versions: versionsSlice, Source: filename, Directory: directory}, err == nil, err } } return versions, found, err } // parseVersion parses the raw version func parseVersion(rawVersions string) []string { var versions []string for _, version := range strings.Split(rawVersions, " ") { version = strings.TrimSpace(version) if len(version) > 0 { versions = append(versions, version) } } return versions } func variableVersionName(toolName string) string { return fmt.Sprintf("ASDF_%s_VERSION", strings.ToUpper(toolName)) } ================================================ FILE: internal/resolve/resolve_test.go ================================================ package resolve import ( "fmt" "os" "path/filepath" "strings" "testing" "github.com/asdf-vm/asdf/internal/config" "github.com/asdf-vm/asdf/internal/plugins" "github.com/asdf-vm/asdf/internal/repotest" "github.com/stretchr/testify/assert" ) const testPluginName = "test-plugin" func TestVersion(t *testing.T) { testDataDir := t.TempDir() currentDir := t.TempDir() conf := config.Config{DataDir: testDataDir, DefaultToolVersionsFilename: ".tool-versions", ConfigFile: "testdata/asdfrc"} _, err := repotest.InstallPlugin("dummy_plugin", conf.DataDir, testPluginName) assert.Nil(t, err) plugin := plugins.New(conf, testPluginName) t.Run("returns empty slice when non-existent version passed", func(t *testing.T) { toolVersion, found, err := Version(conf, plugin, t.TempDir()) assert.Nil(t, err) assert.False(t, found) assert.Empty(t, toolVersion.Versions) }) t.Run("returns single version from .tool-versions file", func(t *testing.T) { // write a version file data := []byte(fmt.Sprintf("%s 1.2.3", testPluginName)) err = os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666) toolVersion, found, err := Version(conf, plugin, currentDir) assert.Nil(t, err) assert.True(t, found) assert.Equal(t, toolVersion.Versions, []string{"1.2.3"}) }) t.Run("returns version from env when env variable set", func(t *testing.T) { // Set env t.Setenv(fmt.Sprintf("ASDF_%s_VERSION", strings.ToUpper(testPluginName)), "2.3.4") // write a version file data := []byte(fmt.Sprintf("%s 1.2.3", testPluginName)) err = os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666) // assert env variable takes precedence toolVersion, found, err := Version(conf, plugin, currentDir) assert.Nil(t, err) assert.True(t, found) assert.Equal(t, toolVersion.Versions, []string{"2.3.4"}) }) t.Run("returns single version from .tool-versions file in parent directory", func(t *testing.T) { // write a version file data := []byte(fmt.Sprintf("%s 1.2.3", testPluginName)) err = os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666) subDir := filepath.Join(currentDir, "subdir") err = os.MkdirAll(subDir, 0o777) assert.Nil(t, err) toolVersion, found, err := Version(conf, plugin, subDir) assert.Nil(t, err) assert.True(t, found) assert.Equal(t, toolVersion.Versions, []string{"1.2.3"}) }) } func TestFindVersionsInDir(t *testing.T) { testDataDir := t.TempDir() conf := config.Config{DataDir: testDataDir, DefaultToolVersionsFilename: ".tool-versions", ConfigFile: "testdata/asdfrc"} _, err := repotest.InstallPlugin("dummy_plugin", conf.DataDir, testPluginName) assert.Nil(t, err) plugin := plugins.New(conf, testPluginName) t.Run("when no versions set returns found false", func(t *testing.T) { currentDir := t.TempDir() versions, found, err := findVersionsInDir(conf, plugin, currentDir) assert.Empty(t, versions) assert.False(t, found) assert.Nil(t, err) }) t.Run("when version is set returns found true and version", func(t *testing.T) { currentDir := t.TempDir() data := []byte(fmt.Sprintf("%s 1.2.3", testPluginName)) err = os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666) toolVersion, found, err := findVersionsInDir(conf, plugin, currentDir) assert.Equal(t, toolVersion.Versions, []string{"1.2.3"}) assert.True(t, found) assert.Nil(t, err) }) t.Run("when multiple versions present in .tool-versions returns found true and versions", func(t *testing.T) { currentDir := t.TempDir() data := []byte(fmt.Sprintf("%s 1.2.3 2.3.4", testPluginName)) err = os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666) toolVersion, found, err := findVersionsInDir(conf, plugin, currentDir) assert.Equal(t, toolVersion.Versions, []string{"1.2.3", "2.3.4"}) assert.True(t, found) assert.Nil(t, err) }) t.Run("when DefaultToolVersionsFilename is set reads from file with that name if exists", func(t *testing.T) { conf := config.Config{DataDir: testDataDir, DefaultToolVersionsFilename: "custom-file"} currentDir := t.TempDir() data := []byte(fmt.Sprintf("%s 1.2.3 2.3.4", testPluginName)) err = os.WriteFile(filepath.Join(currentDir, "custom-file"), data, 0o666) toolVersion, found, err := findVersionsInDir(conf, plugin, currentDir) assert.Equal(t, toolVersion.Versions, []string{"1.2.3", "2.3.4"}) assert.True(t, found) assert.Nil(t, err) }) t.Run("when .tool-version exists and legacy file support is on looks up version in .tool-versions", func(t *testing.T) { currentDir := t.TempDir() data := []byte(fmt.Sprintf("%s 1.2.3", testPluginName)) err = os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666) assert.NoError(t, err) data = []byte("2.3.4 3.4.5") err = os.WriteFile(filepath.Join(currentDir, ".dummy-version"), data, 0o666) assert.NoError(t, err) toolVersion, found, err := findVersionsInDir(conf, plugin, currentDir) assert.Equal(t, toolVersion.Versions, []string{"1.2.3"}) assert.True(t, found) assert.NoError(t, err) }) t.Run("when .tool-version does not exist and legacy file support is on looks up version in legacy file", func(t *testing.T) { currentDir := t.TempDir() data := []byte("1.2.3 2.3.4") err = os.WriteFile(filepath.Join(currentDir, ".dummy-version"), data, 0o666) assert.NoError(t, err) toolVersion, found, err := findVersionsInDir(conf, plugin, currentDir) assert.Equal(t, toolVersion.Versions, []string{"1.2.3", "2.3.4"}) assert.True(t, found) assert.NoError(t, err) }) } func TestFindVersionsLegacyFiles(t *testing.T) { testDataDir := t.TempDir() conf := config.Config{DataDir: testDataDir} _, err := repotest.InstallPlugin("dummy_plugin", conf.DataDir, testPluginName) assert.Nil(t, err) plugin := plugins.New(conf, testPluginName) t.Run("when given tool that lacks list-legacy-filenames callback returns empty versions list", func(t *testing.T) { pluginName := "foobar" _, err := repotest.InstallPlugin("dummy_plugin_no_download", conf.DataDir, pluginName) assert.Nil(t, err) plugin := plugins.New(conf, pluginName) toolVersion, found, err := findVersionsInLegacyFile(plugin, t.TempDir()) assert.Empty(t, toolVersion.Versions) assert.False(t, found) assert.Nil(t, err) }) t.Run("when given tool that has a list-legacy-filenames callback but file not found returns empty versions list", func(t *testing.T) { toolVersion, found, err := findVersionsInLegacyFile(plugin, t.TempDir()) assert.Empty(t, toolVersion.Versions) assert.False(t, found) assert.Nil(t, err) }) t.Run("when given tool that has a list-legacy-filenames callback and file found returns populated versions list", func(t *testing.T) { // write legacy version file currentDir := t.TempDir() data := []byte("1.2.3") err = os.WriteFile(filepath.Join(currentDir, ".dummy-version"), data, 0o666) assert.Nil(t, err) toolVersion, found, err := findVersionsInLegacyFile(plugin, currentDir) assert.Equal(t, toolVersion.Versions, []string{"1.2.3"}) assert.True(t, found) assert.Nil(t, err) }) } func TestFindVersionsInEnv(t *testing.T) { t.Run("when env variable isn't set returns empty list of versions", func(t *testing.T) { versions, envVariableName, found := findVersionsInEnv("non-existent") assert.False(t, found) assert.Empty(t, versions) assert.Equal(t, envVariableName, "ASDF_NON-EXISTENT_VERSION") }) t.Run("when env variable is set returns version", func(t *testing.T) { os.Setenv("ASDF_LUA_VERSION", "5.4.5") versions, envVariableName, found := findVersionsInEnv("lua") assert.True(t, found) assert.Equal(t, versions, []string{"5.4.5"}) assert.Equal(t, envVariableName, "ASDF_LUA_VERSION") os.Unsetenv("ASDF_LUA_VERSION") }) t.Run("when env variable is set to multiple versions", func(t *testing.T) { os.Setenv("ASDF_LUA_VERSION", "5.4.5 5.4.6") versions, envVariableName, found := findVersionsInEnv("lua") assert.True(t, found) assert.Equal(t, versions, []string{"5.4.5", "5.4.6"}) assert.Equal(t, envVariableName, "ASDF_LUA_VERSION") os.Unsetenv("ASDF_LUA_VERSION") }) } func TestVariableVersionName(t *testing.T) { tests := []struct { input string output string }{ { input: "ruby", output: "ASDF_RUBY_VERSION", }, { input: "lua", output: "ASDF_LUA_VERSION", }, { input: "foo-bar", output: "ASDF_FOO-BAR_VERSION", }, } for _, tt := range tests { t.Run(fmt.Sprintf("input: %s, output: %s", tt.input, tt.output), func(t *testing.T) { assert.Equal(t, tt.output, variableVersionName(tt.input)) }) } } ================================================ FILE: internal/resolve/testdata/asdfrc ================================================ legacy_version_file = yes ================================================ FILE: internal/shims/shims.go ================================================ // Package shims manages writing and parsing of asdf shim scripts. package shims import ( "fmt" "io" "io/fs" "os" "os/exec" "path" "path/filepath" "slices" "strings" "github.com/asdf-vm/asdf/internal/config" "github.com/asdf-vm/asdf/internal/hook" "github.com/asdf-vm/asdf/internal/installs" "github.com/asdf-vm/asdf/internal/paths" "github.com/asdf-vm/asdf/internal/plugins" "github.com/asdf-vm/asdf/internal/resolve" "github.com/asdf-vm/asdf/internal/toolversions" "golang.org/x/sys/unix" ) const shimDirName = "shims" // UnknownCommandError is an error returned when a shim is not found type UnknownCommandError struct { shim string } func (e UnknownCommandError) Error() string { return fmt.Sprintf("unknown command: %s", e.shim) } // NoVersionSetError is returned when shim is found but no version matches type NoVersionSetError struct { shim string } func (e NoVersionSetError) Error() string { return fmt.Sprintf("no versions set for %s", e.shim) } // NoExecutableForPluginError is returned when a compatible version is found // but no executable matching the name is located. type NoExecutableForPluginError struct { shim string tools []string versions []string } func (e NoExecutableForPluginError) Error() string { return fmt.Sprintf("No %s executable found for %s %s", e.shim, strings.Join(e.tools, ", "), strings.Join(e.versions, ", ")) } // FindExecutable takes a shim name and a current directory and returns the path // to the executable that the shim resolves to. func FindExecutable(conf config.Config, shimName, currentDirectory string) (path string, plugin plugins.Plugin, version string, found bool, err error) { shimPath := Path(conf, shimName) if _, err := os.Stat(shimPath); err != nil { return "", plugins.Plugin{}, "", false, UnknownCommandError{shim: shimName} } toolVersions, err := GetToolsAndVersionsFromShimFile(shimPath) if err != nil { return "", plugins.Plugin{}, "", false, err } existingPluginToolVersions := make(map[plugins.Plugin]resolve.ToolVersions) // loop over tools and check if the plugin for them still exists for _, shimToolVersion := range toolVersions { plugin := plugins.New(conf, shimToolVersion.Name) if plugin.Exists() == nil { // If a shim template is found, we can return it before looping through versions shimTemplate, err := plugin.ShimTemplatePath(shimName) if err == nil { return shimTemplate, plugin, "", true, nil } versions, found, err := resolve.Version(conf, plugin, currentDirectory) if err != nil { return "", plugins.Plugin{}, "", false, nil } if found { tempVersions := toolversions.Intersect(versions.Versions, shimToolVersion.Versions) if slices.Contains(versions.Versions, "system") { tempVersions = append(tempVersions, "system") } parsedVersions := toolversions.ParseSlice(versions.Versions) for _, parsedVersion := range parsedVersions { if parsedVersion.Type == "path" { tempVersions = append(tempVersions, toolversions.Format(parsedVersion)) } } versions.Versions = tempVersions if len(versions.Versions) > 0 { existingPluginToolVersions[plugin] = versions } } } } if len(existingPluginToolVersions) == 0 { return "", plugins.Plugin{}, "", false, NoVersionSetError{shim: shimName} } for plugin, toolVersions := range existingPluginToolVersions { for _, version := range toolVersions.Versions { parsedVersion := toolversions.Parse(version) if parsedVersion.Type == "system" { if executablePath, found := SystemExecutableOnPath(conf, shimName); found { return executablePath, plugin, version, true, nil } break } if parsedVersion.Type == "path" { executablePath, err := GetExecutablePath(conf, plugin, shimName, parsedVersion) if err == nil { return executablePath, plugin, version, true, nil } break } executablePath, err := GetExecutablePath(conf, plugin, shimName, parsedVersion) if err == nil { return executablePath, plugin, version, true, nil } } } tools := []string{} versions := []string{} for plugin, toolVersions := range existingPluginToolVersions { tools = append(tools, plugin.Name) versions = append(versions, toolVersions.Versions...) } return "", plugins.Plugin{}, "", false, NoExecutableForPluginError{shim: shimName, tools: tools, versions: versions} } // SystemExecutableOnPath returns the path to the system executable if found, // removes asdf shim directory from search func SystemExecutableOnPath(conf config.Config, executableName string) (string, bool) { currentPath := os.Getenv("PATH") executablePath, err := ExecutableOnPath(paths.RemoveFromPath(currentPath, Directory(conf)), executableName) return executablePath, err == nil } // ExecutableOnPath returns the path to an executable if one is found on the // provided paths. `path` must be in the same format as the `PATH` environment // variable. func ExecutableOnPath(path, command string) (string, error) { currentPath := os.Getenv("PATH") defer os.Setenv("PATH", currentPath) os.Setenv("PATH", path) return exec.LookPath(command) } // GetExecutablePath returns the path of the executable func GetExecutablePath(conf config.Config, plugin plugins.Plugin, shimName string, version toolversions.Version) (string, error) { executables, err := ToolExecutables(conf, plugin, version) if err != nil { return "", err } executable := "" for _, executablePath := range executables { if filepath.Base(executablePath) == shimName { executable = executablePath } } path, err := getCustomExecutablePath(conf, plugin, shimName, version, executable) if err == nil { return path, err } if executable != "" { return executable, nil } return "", fmt.Errorf("executable not found") } // GetToolsAndVersionsFromShimFile takes a file path and parses out the tools // and versions present in it and returns them as a slice containing info in // ToolVersions structs. func GetToolsAndVersionsFromShimFile(shimPath string) (versions []toolversions.ToolVersions, err error) { contents, err := os.ReadFile(shimPath) if err != nil { return versions, err } versions = parse(string(contents)) versions = toolversions.Unique(versions) return versions, err } func getCustomExecutablePath(conf config.Config, plugin plugins.Plugin, shimName string, version toolversions.Version, executablePath string) (string, error) { var stdOut strings.Builder var stdErr strings.Builder installPath := installs.InstallPath(conf, plugin, version) env := map[string]string{"ASDF_INSTALL_TYPE": "version"} relativePath, err := filepath.Rel(installPath, executablePath) if err != nil { return "", err } err = plugin.RunCallback("exec-path", []string{installPath, shimName, relativePath}, env, &stdOut, &stdErr) if err != nil { return "", err } return filepath.Join(installPath, strings.TrimSpace(stdOut.String())), err } // RemoveAll removes all shim scripts func RemoveAll(conf config.Config) error { shimDir := filepath.Join(conf.DataDir, shimDirName) entries, err := os.ReadDir(shimDir) if err != nil { if _, ok := err.(*fs.PathError); ok { // if directory doesn't exist we can just return because no shims can // possibly exist. return nil } return err } for _, entry := range entries { os.RemoveAll(path.Join(shimDir, entry.Name())) } return nil } // GenerateAll generates shims for all executables of every version of every // plugin. func GenerateAll(conf config.Config, stdOut io.Writer, stdErr io.Writer) error { plugins, err := plugins.List(conf, false, false) if err != nil { return err } for _, plugin := range plugins { err := GenerateForPluginVersions(conf, plugin, stdOut, stdErr) if err != nil { return err } } return nil } // GenerateForPluginVersions generates all shims for all installed versions of // a tool. func GenerateForPluginVersions(conf config.Config, plugin plugins.Plugin, stdOut io.Writer, stdErr io.Writer) error { installedVersions, err := installs.Installed(conf, plugin) if err != nil { return err } for _, version := range installedVersions { parsedVersion := toolversions.Parse(version) GenerateForVersion(conf, plugin, parsedVersion, stdOut, stdErr) } return nil } // GenerateForVersion loops over all the executable files found for a tool and // generates a shim for each one func GenerateForVersion(conf config.Config, plugin plugins.Plugin, version toolversions.Version, stdOut io.Writer, stdErr io.Writer) error { err := hook.RunWithOutput(conf, fmt.Sprintf("pre_asdf_reshim_%s", plugin.Name), []string{toolversions.Format(version)}, stdOut, stdErr) if err != nil { return err } executables, err := ToolExecutables(conf, plugin, version) if err != nil { return err } for _, executablePath := range executables { err := Write(conf, plugin, version, executablePath) if err != nil { return err } } err = hook.RunWithOutput(conf, fmt.Sprintf("post_asdf_reshim_%s", plugin.Name), []string{toolversions.Format(version)}, stdOut, stdErr) if err != nil { return err } return nil } // Write generates a shim script and writes it to disk func Write(conf config.Config, plugin plugins.Plugin, version toolversions.Version, executablePath string) error { err := ensureShimDirExists(conf) if err != nil { return err } shimName := filepath.Base(executablePath) shimPath := Path(conf, shimName) versions := []toolversions.ToolVersions{{Name: plugin.Name, Versions: []string{toolversions.Format(version)}}} if _, err := os.Stat(shimPath); err == nil { oldVersions, err := GetToolsAndVersionsFromShimFile(shimPath) if err != nil { return err } versions = toolversions.Unique(append(versions, oldVersions...)) } return os.WriteFile(shimPath, []byte(encode(shimName, versions)), 0o777) } // Path returns the path for a shim script func Path(conf config.Config, shimName string) string { return filepath.Join(conf.DataDir, shimDirName, shimName) } // Directory returns the path to the shims directory for the current // configuration. func Directory(conf config.Config) string { return filepath.Join(conf.DataDir, shimDirName) } func ensureShimDirExists(conf config.Config) error { return os.MkdirAll(filepath.Join(conf.DataDir, shimDirName), 0o777) } // ToolExecutables returns a slice of executables for a given tool version func ToolExecutables(conf config.Config, plugin plugins.Plugin, version toolversions.Version) (executables []string, err error) { paths, err := ExecutablePaths(conf, plugin, version) if err != nil { return []string{}, err } for _, path := range paths { entries, err := os.ReadDir(path) if _, ok := err.(*os.PathError); err != nil && !ok { return executables, err } for _, entry := range entries { // If entry is dir or cannot be executed by the current user ignore it filePath := filepath.Join(path, entry.Name()) if entry.IsDir() || unix.Access(filePath, unix.X_OK) != nil { continue } executables = append(executables, filePath) } } return executables, err } // ExecutablePaths returns a slice of absolute directory paths that tool // executables are contained in. func ExecutablePaths(conf config.Config, plugin plugins.Plugin, version toolversions.Version) (paths []string, err error) { dirs, err := ExecutableDirs(plugin) if err != nil { return []string{}, err } shimsDir, err := os.Stat(path.Join(plugin.Dir, "shims")) if err == nil && shimsDir.IsDir() { paths = append(paths, path.Join(plugin.Dir, "shims")) } installPath := installs.InstallPath(conf, plugin, version) return append(paths, dirsToPaths(dirs, installPath)...), nil } // ExecutableDirs returns a slice of relative directory names that tool executables are // contained in, *inside installs* func ExecutableDirs(plugin plugins.Plugin) ([]string, error) { var stdOut strings.Builder var stdErr strings.Builder err := plugin.RunCallback("list-bin-paths", []string{}, map[string]string{}, &stdOut, &stdErr) if err != nil { if _, ok := err.(plugins.NoCallbackError); ok { // assume all executables are located in /bin directory return []string{"bin"}, nil } return []string{}, err } // use output from list-bin-paths to determine locations for executables rawDirs := strings.Split(stdOut.String(), " ") var dirs []string for _, dir := range rawDirs { dirs = append(dirs, strings.TrimSpace(dir)) } return dirs, nil } func parse(contents string) (versions []toolversions.ToolVersions) { lines := strings.Split(contents, "\n") for _, line := range lines { if strings.HasPrefix(line, "# asdf-plugin:") { segments := strings.Split(line, " ") // if doesn't have expected number of elements on line skip if len(segments) >= 4 { versions = append(versions, toolversions.ToolVersions{Name: segments[2], Versions: []string{segments[3]}}) } } } return versions } func encode(shimName string, toolVersions []toolversions.ToolVersions) string { var content string content = "#!/usr/bin/env bash\n" // Add all asdf-plugin comments for _, tool := range toolVersions { for _, version := range tool.Versions { content += fmt.Sprintf("# asdf-plugin: %s %s\n", tool.Name, version) } } // Add call asdf exec to actually run real command content += fmt.Sprintf("exec asdf exec \"%s\" \"$@\"", shimName) return content } func dirsToPaths(dirs []string, root string) (paths []string) { for _, dir := range dirs { paths = append(paths, filepath.Join(root, dir)) } return paths } ================================================ FILE: internal/shims/shims_test.go ================================================ package shims import ( "errors" "fmt" "os" "path/filepath" "strings" "testing" "github.com/asdf-vm/asdf/internal/config" "github.com/asdf-vm/asdf/internal/installs" "github.com/asdf-vm/asdf/internal/installtest" "github.com/asdf-vm/asdf/internal/plugins" "github.com/asdf-vm/asdf/internal/repotest" "github.com/asdf-vm/asdf/internal/toolversions" "github.com/stretchr/testify/assert" "golang.org/x/sys/unix" ) const testPluginName = "lua" func TestFindExecutable(t *testing.T) { version := "1.1.0" conf, plugin := generateConfig(t) installVersion(t, conf, plugin, version) installVersion(t, conf, plugin, "2.0.0") stdout, stderr := buildOutputs() assert.Nil(t, GenerateAll(conf, &stdout, &stderr)) currentDir := t.TempDir() t.Run("returns error when shim with name does not exist", func(t *testing.T) { executable, _, version, found, err := FindExecutable(conf, "foo", currentDir) assert.Empty(t, executable) assert.False(t, found) assert.Empty(t, version) assert.Equal(t, err.(UnknownCommandError).Error(), "unknown command: foo") }) t.Run("returns error when shim is present but no version is set", func(t *testing.T) { executable, _, version, found, err := FindExecutable(conf, "dummy", currentDir) assert.Empty(t, executable) assert.False(t, found) assert.Empty(t, version) assert.Equal(t, err.(NoVersionSetError).Error(), "no versions set for dummy") }) t.Run("returns string containing path to executable when found", func(t *testing.T) { // write a version file data := []byte("lua 1.1.0") assert.Nil(t, os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666)) executable, gotPlugin, version, found, err := FindExecutable(conf, "dummy", currentDir) assert.Equal(t, filepath.Base(filepath.Dir(filepath.Dir(executable))), "1.1.0") assert.Equal(t, filepath.Base(executable), "dummy") assert.Equal(t, plugin, gotPlugin) assert.Equal(t, version, "1.1.0") assert.True(t, found) assert.Nil(t, err) }) t.Run("returns path to executable with first version when multiple versions are set", func(t *testing.T) { // write a version file data := []byte("lua 1.1.0 3.0.0 2.0.0") assert.Nil(t, os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666)) executable, gotPlugin, version, found, err := FindExecutable(conf, "dummy", currentDir) assert.Equal(t, filepath.Base(filepath.Dir(filepath.Dir(executable))), "1.1.0") assert.Equal(t, filepath.Base(executable), "dummy") assert.Equal(t, plugin, gotPlugin) assert.Equal(t, version, "1.1.0") assert.True(t, found) assert.Nil(t, err) }) t.Run("returns string containing path to system executable when system version set", func(t *testing.T) { // Create dummy `ls` executable versionStruct := toolversions.Version{Type: "version", Value: version} path := filepath.Join(installs.InstallPath(conf, plugin, versionStruct), "bin", "ls") assert.Nil(t, os.WriteFile(path, []byte("echo 'I'm ls'"), 0o777)) // write system version to version file toolpath := filepath.Join(currentDir, ".tool-versions") assert.Nil(t, os.WriteFile(toolpath, []byte("lua system\n"), 0o666)) assert.Nil(t, GenerateAll(conf, &stdout, &stderr)) executable, gotPlugin, version, found, err := FindExecutable(conf, "ls", currentDir) assert.Equal(t, plugin, gotPlugin) assert.Equal(t, version, "system") assert.True(t, found) assert.Nil(t, err) // see that it actually returns path to system ls assert.Equal(t, filepath.Base(executable), "ls") assert.NotEqual(t, executable, path) }) t.Run("returns path to executable on path when path version set", func(t *testing.T) { // write system version to version file toolpath := filepath.Join(currentDir, ".tool-versions") dir := installs.InstallPath(conf, plugin, toolversions.Version{Type: "version", Value: "1.1.0"}) pathVersion := fmt.Sprintf("path:%s/./", dir) assert.Nil(t, os.WriteFile(toolpath, []byte(fmt.Sprintf("lua %s\n", pathVersion)), 0o666)) assert.Nil(t, GenerateAll(conf, &stdout, &stderr)) executable, gotPlugin, version, found, err := FindExecutable(conf, "dummy", currentDir) assert.Equal(t, plugin, gotPlugin) assert.Equal(t, version, pathVersion) assert.True(t, found) assert.Nil(t, err) // see that it actually returns path to system ls assert.Equal(t, filepath.Base(executable), "dummy") assert.True(t, strings.HasPrefix(executable, dir)) }) t.Run("returns string containing path to executable when shim template in plugin is set", func(t *testing.T) { // write a version file data := []byte("lua 1.1.0") assert.Nil(t, os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666)) // write a shim template to the plugin shims dir setupShimTemplate(t, plugin, "dummy", "echo 'shim template'") executable, gotPlugin, version, found, err := FindExecutable(conf, "dummy", currentDir) assert.Nil(t, err) relativePath, err := filepath.Rel(conf.DataDir, executable) assert.Nil(t, err) assert.Equal(t, "plugins/lua/shims/dummy", relativePath) assert.Equal(t, "dummy", filepath.Base(executable)) assert.Equal(t, plugin, gotPlugin) assert.Equal(t, "", version) assert.True(t, found) }) } func TestFindExecutable_Ref(t *testing.T) { version := "ref:v1.1.0" conf, plugin := generateConfig(t) installVersion(t, conf, plugin, version) stdout, stderr := buildOutputs() assert.Nil(t, GenerateAll(conf, &stdout, &stderr)) currentDir := t.TempDir() t.Run("returns string containing path to ref installed executable when found", func(t *testing.T) { // write a version file data := []byte("lua ref:v1.1.0") assert.Nil(t, os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666)) executable, gotPlugin, version, found, err := FindExecutable(conf, "dummy", currentDir) assert.Nil(t, err) assert.True(t, found) assert.Equal(t, "ref:v1.1.0", version) assert.Equal(t, plugin, gotPlugin) assert.Equal(t, "dummy", filepath.Base(executable)) assert.Equal(t, "ref-v1.1.0", filepath.Base(filepath.Dir(filepath.Dir(executable)))) }) } func TestGetExecutablePath(t *testing.T) { version := toolversions.Version{Type: "version", Value: "1.1.0"} conf, plugin := generateConfig(t) installVersion(t, conf, plugin, version.Value) t.Run("returns path to executable", func(t *testing.T) { path, err := GetExecutablePath(conf, plugin, "dummy", version) assert.Nil(t, err) assert.Equal(t, filepath.Base(path), "dummy") assert.Equal(t, filepath.Base(filepath.Dir(filepath.Dir(path))), version.Value) }) t.Run("returns error when executable with name not found", func(t *testing.T) { path, err := GetExecutablePath(conf, plugin, "foo", version) assert.ErrorContains(t, err, "executable not found") assert.Equal(t, path, "") }) t.Run("returns custom path when plugin has exec-path callback", func(t *testing.T) { // Create exec-path callback installDummyExecPathScript(t, conf, plugin, version, "dummy", "echo 'bin/custom/dummy'") path, err := GetExecutablePath(conf, plugin, "dummy", version) assert.Nil(t, err) assert.Equal(t, filepath.Base(filepath.Dir(path)), "custom") // Doesn't contain any trailing whitespace (newlines as the last char are common) assert.Equal(t, path, strings.TrimSpace(path)) }) t.Run("returns default path when plugin has exec-path callback that prints third argument", func(t *testing.T) { // Create exec-path callback installDummyExecPathScript(t, conf, plugin, version, "dummy", "echo \"$3\"") path, err := GetExecutablePath(conf, plugin, "dummy", version) assert.Nil(t, err) assert.Equal(t, filepath.Base(path), "dummy") assert.Equal(t, filepath.Base(filepath.Dir(path)), "bin") // Doesn't contain any trailing whitespace (newlines as the last char are common) assert.Equal(t, path, strings.TrimSpace(path)) }) } func TestRemoveAll(t *testing.T) { version := "1.1.0" conf, plugin := generateConfig(t) installVersion(t, conf, plugin, version) executables, err := ToolExecutables(conf, plugin, toolversions.Version{Type: "version", Value: version}) assert.Nil(t, err) stdout, stderr := buildOutputs() t.Run("removes all files in shim directory", func(t *testing.T) { assert.Nil(t, GenerateAll(conf, &stdout, &stderr)) assert.Nil(t, RemoveAll(conf)) // check for generated shims for _, executable := range executables { _, err := os.Stat(Path(conf, filepath.Base(executable))) assert.True(t, errors.Is(err, os.ErrNotExist)) } }) t.Run("does not return error when shims directory does not exist", func(t *testing.T) { shimDir := Directory(conf) assert.Nil(t, os.RemoveAll(shimDir)) assert.Nil(t, RemoveAll(conf)) }) } func TestGenerateAll(t *testing.T) { version := "1.1.0" version2 := "2.0.0" conf, plugin := generateConfig(t) installVersion(t, conf, plugin, version) installPlugin(t, conf, "dummy_plugin", "ruby") installVersion(t, conf, plugin, version2) executables, err := ToolExecutables(conf, plugin, toolversions.Version{Type: "version", Value: version}) assert.Nil(t, err) stdout, stderr := buildOutputs() t.Run("generates shim script for every executable in every version of every tool", func(t *testing.T) { assert.Nil(t, GenerateAll(conf, &stdout, &stderr)) // check for generated shims for _, executable := range executables { shimName := filepath.Base(executable) shimPath := Path(conf, shimName) assert.Nil(t, unix.Access(shimPath, unix.X_OK)) // shim exists and has expected contents content, err := os.ReadFile(shimPath) assert.Nil(t, err) want := fmt.Sprintf("#!/usr/bin/env bash\n# asdf-plugin: lua 2.0.0\n# asdf-plugin: lua 1.1.0\nexec asdf exec \"%s\" \"$@\"", shimName) assert.Equal(t, want, string(content)) } }) } func TestGenerateForPluginVersions(t *testing.T) { t.Setenv("ASDF_CONFIG_FILE", "testdata/asdfrc") version := "1.1.0" version2 := "2.0.0" conf, plugin := generateConfig(t) installVersion(t, conf, plugin, version) installVersion(t, conf, plugin, version2) executables, err := ToolExecutables(conf, plugin, toolversions.Version{Type: "version", Value: version}) assert.Nil(t, err) stdout, stderr := buildOutputs() t.Run("generates shim script for every executable in every version the tool", func(t *testing.T) { assert.Nil(t, GenerateForPluginVersions(conf, plugin, &stdout, &stderr)) // check for generated shims for _, executable := range executables { shimName := filepath.Base(executable) shimPath := Path(conf, shimName) assert.Nil(t, unix.Access(shimPath, unix.X_OK)) // shim exists and has expected contents content, err := os.ReadFile(shimPath) assert.Nil(t, err) want := fmt.Sprintf("#!/usr/bin/env bash\n# asdf-plugin: lua 2.0.0\n# asdf-plugin: lua 1.1.0\nexec asdf exec \"%s\" \"$@\"", shimName) assert.Equal(t, want, string(content)) } }) t.Run("runs pre and post reshim hooks", func(t *testing.T) { stdout, stderr := buildOutputs() assert.Nil(t, GenerateForPluginVersions(conf, plugin, &stdout, &stderr)) want := "pre_reshim 1.1.0\npost_reshim 1.1.0\npre_reshim 2.0.0\npost_reshim 2.0.0\n" assert.Equal(t, want, stdout.String()) }) } func TestGenerateForVersion(t *testing.T) { version := toolversions.Version{Type: "version", Value: "1.1.0"} version2 := toolversions.Version{Type: "version", Value: "2.0.0"} conf, plugin := generateConfig(t) installVersion(t, conf, plugin, version.Value) installVersion(t, conf, plugin, version2.Value) executables, err := ToolExecutables(conf, plugin, version) assert.Nil(t, err) t.Run("generates shim script for every executable in version", func(t *testing.T) { stdout, stderr := buildOutputs() assert.Nil(t, GenerateForVersion(conf, plugin, version, &stdout, &stderr)) // check for generated shims for _, executable := range executables { shimName := filepath.Base(executable) shimPath := Path(conf, shimName) assert.Nil(t, unix.Access(shimPath, unix.X_OK)) } }) t.Run("updates existing shims for every executable in version", func(t *testing.T) { stdout, stderr := buildOutputs() assert.Nil(t, GenerateForVersion(conf, plugin, version, &stdout, &stderr)) assert.Nil(t, GenerateForVersion(conf, plugin, version2, &stdout, &stderr)) // check for generated shims for _, executable := range executables { shimName := filepath.Base(executable) shimPath := Path(conf, shimName) assert.Nil(t, unix.Access(shimPath, unix.X_OK)) } }) } func TestWrite(t *testing.T) { version := toolversions.Version{Type: "version", Value: "1.1.0"} version2 := toolversions.Version{Type: "version", Value: "2.0.0"} conf, plugin := generateConfig(t) installVersion(t, conf, plugin, version.Value) installVersion(t, conf, plugin, version2.Value) executables, err := ToolExecutables(conf, plugin, version) executable := executables[0] assert.Nil(t, err) t.Run("writes a new shim file when doesn't exist", func(t *testing.T) { executable := executables[0] err = Write(conf, plugin, version, executable) assert.Nil(t, err) // shim is executable shimName := filepath.Base(executable) shimPath := Path(conf, shimName) assert.Nil(t, unix.Access(shimPath, unix.X_OK)) // shim exists and has expected contents content, err := os.ReadFile(shimPath) assert.Nil(t, err) want := "#!/usr/bin/env bash\n# asdf-plugin: lua 1.1.0\nexec asdf exec \"dummy\" \"$@\"" assert.Equal(t, want, string(content)) os.Remove(shimPath) }) t.Run("updates an existing shim file when already present", func(t *testing.T) { // Write same shim for two versions assert.Nil(t, Write(conf, plugin, version, executable)) assert.Nil(t, Write(conf, plugin, version2, executable)) // shim is still executable shimName := filepath.Base(executable) shimPath := Path(conf, shimName) assert.Nil(t, unix.Access(shimPath, unix.X_OK)) // has expected contents content, err := os.ReadFile(shimPath) assert.Nil(t, err) want := "#!/usr/bin/env bash\n# asdf-plugin: lua 2.0.0\n# asdf-plugin: lua 1.1.0\nexec asdf exec \"dummy\" \"$@\"" assert.Equal(t, want, string(content)) os.Remove(shimPath) }) t.Run("doesn't add the same version to a shim file twice", func(t *testing.T) { assert.Nil(t, Write(conf, plugin, version, executable)) assert.Nil(t, Write(conf, plugin, version, executable)) // Shim doesn't contain any duplicate lines shimPath := Path(conf, filepath.Base(executable)) content, err := os.ReadFile(shimPath) assert.Nil(t, err) want := "#!/usr/bin/env bash\n# asdf-plugin: lua 1.1.0\nexec asdf exec \"dummy\" \"$@\"" assert.Equal(t, want, string(content)) os.Remove(shimPath) }) } func TestToolExecutables(t *testing.T) { version := toolversions.Version{Type: "version", Value: "1.1.0"} conf, plugin := generateConfig(t) installVersion(t, conf, plugin, version.Value) t.Run("returns list of executables for plugin", func(t *testing.T) { executables, err := ToolExecutables(conf, plugin, version) assert.Nil(t, err) var filenames []string for _, executablePath := range executables { assert.True(t, strings.HasPrefix(executablePath, conf.DataDir)) filenames = append(filenames, filepath.Base(executablePath)) } assert.Equal(t, filenames, []string{"dummy"}) }) t.Run("returns list of executables for version installed in arbitrary directory", func(t *testing.T) { // Reference regular install by path to validate this behavior path := installs.InstallPath(conf, plugin, version) executables, err := ToolExecutables(conf, plugin, toolversions.Version{Type: "path", Value: path}) assert.Nil(t, err) var filenames []string for _, executablePath := range executables { assert.True(t, strings.HasPrefix(executablePath, conf.DataDir)) filenames = append(filenames, filepath.Base(executablePath)) } assert.Equal(t, filenames, []string{"dummy"}) }) t.Run("returns list of executables even when one directory printed by list-bin-paths doesn't exist", func(t *testing.T) { // foo is first in list returned by list-bin-paths but doesn't exist, do // we still get the executables in the bin/ dir? repotest.WritePluginCallback(plugin.Dir, "list-bin-paths", "#!/usr/bin/env bash\necho 'foo bin'") executables, err := ToolExecutables(conf, plugin, version) assert.Nil(t, err) var filenames []string for _, executablePath := range executables { assert.True(t, strings.HasPrefix(executablePath, conf.DataDir)) filenames = append(filenames, filepath.Base(executablePath)) } assert.Equal(t, filenames, []string{"dummy"}) }) } func TestExecutablePaths(t *testing.T) { conf, plugin := generateConfig(t) installVersion(t, conf, plugin, "1.2.3") t.Run("returns list only containing 'bin' when list-bin-paths callback missing", func(t *testing.T) { executables, err := ExecutablePaths(conf, plugin, toolversions.Version{Type: "version", Value: "1.2.3"}) path := executables[0] assert.Nil(t, err) assert.Equal(t, filepath.Base(filepath.Dir(path)), "1.2.3") assert.Equal(t, filepath.Base(path), "bin") }) t.Run("returns list of executable paths for tool version", func(t *testing.T) { data := []byte("echo 'foo bar'") err := os.WriteFile(filepath.Join(plugin.Dir, "bin", "list-bin-paths"), data, 0o777) assert.Nil(t, err) executables, err := ExecutablePaths(conf, plugin, toolversions.Version{Type: "version", Value: "1.2.3"}) path1 := executables[0] path2 := executables[1] assert.Nil(t, err) assert.Equal(t, filepath.Base(path1), "foo") assert.Equal(t, filepath.Base(path2), "bar") }) t.Run("returns list of executable paths for tool version containing shim templates", func(t *testing.T) { data := []byte("echo 'foo bar'") err := os.WriteFile(filepath.Join(plugin.Dir, "bin", "list-bin-paths"), data, 0o777) assert.Nil(t, err) // write a shim template to the plugin shims dir setupShimTemplate(t, plugin, "dummy", "echo 'shim template'") executables, err := ExecutablePaths(conf, plugin, toolversions.Version{Type: "version", Value: "1.2.3"}) assert.Nil(t, err) path1 := executables[0] path2 := executables[1] path3 := executables[2] assert.Equal(t, "foo", filepath.Base(path2)) assert.Equal(t, "bar", filepath.Base(path3)) relativePath, err := filepath.Rel(conf.DataDir, path1) assert.Nil(t, err) assert.Equal(t, "plugins/lua/shims", relativePath) }) } func TestExecutableDirs(t *testing.T) { conf, plugin := generateConfig(t) installVersion(t, conf, plugin, "1.2.3") t.Run("returns list only containing 'bin' when list-bin-paths callback missing", func(t *testing.T) { executables, err := ExecutableDirs(plugin) assert.Nil(t, err) assert.Equal(t, executables, []string{"bin"}) }) t.Run("returns list of executable paths for tool version", func(t *testing.T) { data := []byte("echo 'foo bar'") err := os.WriteFile(filepath.Join(plugin.Dir, "bin", "list-bin-paths"), data, 0o777) assert.Nil(t, err) executables, err := ExecutableDirs(plugin) assert.Nil(t, err) assert.Equal(t, executables, []string{"foo", "bar"}) }) } // Helper functions func buildOutputs() (strings.Builder, strings.Builder) { var stdout strings.Builder var stderr strings.Builder return stdout, stderr } func generateConfig(t *testing.T) (config.Config, plugins.Plugin) { t.Helper() testDataDir := t.TempDir() conf, err := config.LoadConfig() assert.Nil(t, err) conf.DataDir = testDataDir return conf, installPlugin(t, conf, "dummy_plugin", testPluginName) } func installDummyExecPathScript(t *testing.T, conf config.Config, plugin plugins.Plugin, version toolversions.Version, name, script string) { t.Helper() execPath := filepath.Join(plugin.Dir, "bin", "exec-path") contents := fmt.Sprintf("#!/usr/bin/env bash\n%s\n", script) err := os.WriteFile(execPath, []byte(contents), 0o777) assert.Nil(t, err) installPath := installs.InstallPath(conf, plugin, version) err = os.MkdirAll(filepath.Join(installPath, "bin", "custom"), 0o777) assert.Nil(t, err) err = os.WriteFile(filepath.Join(installPath, "bin", "custom", name), []byte{}, 0o777) assert.Nil(t, err) } func installPlugin(t *testing.T, conf config.Config, fixture, pluginName string) plugins.Plugin { _, err := repotest.InstallPlugin(fixture, conf.DataDir, pluginName) assert.Nil(t, err) return plugins.New(conf, pluginName) } func installVersion(t *testing.T, conf config.Config, plugin plugins.Plugin, version string) { t.Helper() err := installtest.InstallOneVersion(conf, plugin, "version", version) assert.Nil(t, err) } func setupShimTemplate(t *testing.T, plugin plugins.Plugin, shimName string, script string) { t.Helper() shimsDirPath := filepath.Join(plugin.Dir, "shims") os.MkdirAll(shimsDirPath, 0o777) shimPath := filepath.Join(shimsDirPath, shimName) contents := fmt.Sprintf("#!/usr/bin/env bash\n%s\n", script) err := os.WriteFile(shimPath, []byte(contents), 0o777) assert.Nil(t, err) t.Cleanup(func() { err := os.Remove(shimPath) assert.Nil(t, err) }) } ================================================ FILE: internal/shims/testdata/asdfrc ================================================ pre_asdf_reshim_lua = echo pre_reshim $@ post_asdf_reshim_lua = echo post_reshim $@ ================================================ FILE: internal/toolversions/toolversions.go ================================================ // Package toolversions handles reading and writing tools and versions from // asdf's .tool-versions files. It also handles parsing version strings from // .tool-versions files and command line arguments. package toolversions import ( "fmt" "os" "slices" "strings" ) // Version struct represents a single version in asdf. type Version struct { Type string // Must be one of: version, ref, path, system, latest Value string // Any string } // ToolVersions represents a tool along with versions specified for it type ToolVersions struct { Name string Versions []string } // WriteToolVersionsToFile takes a path to a file and writes the new tool and // version data to the file. It creates the file if it does not exist and // updates it if it does. func WriteToolVersionsToFile(filepath string, toolVersions []ToolVersions) error { content, err := os.ReadFile(filepath) if _, ok := err.(*os.PathError); err != nil && !ok { return err } updatedContent := updateContentWithToolVersions(string(content), toolVersions) return os.WriteFile(filepath, []byte(updatedContent), 0o666) } func updateContentWithToolVersions(content string, toolVersions []ToolVersions) string { var output strings.Builder if content != "" { for _, line := range readLines(content) { tokens, comment := parseLine(line) if len(tokens) > 1 { tv := ToolVersions{Name: tokens[0], Versions: tokens[1:]} indexMatching := slices.IndexFunc(toolVersions, func(toolVersion ToolVersions) bool { return toolVersion.Name == tv.Name }) if indexMatching != -1 { // write updated version newTv := toolVersions[indexMatching] newTokens := toolVersionsToTokens(newTv) writeLine(&output, encodeLine(newTokens, comment)) toolVersions = slices.Delete(toolVersions, indexMatching, indexMatching+1) continue } } // write back original line writeLine(&output, line) } } // If any ToolVersions structs remaining, write them to the end of the file if len(toolVersions) > 0 { for _, toolVersion := range toolVersions { newTokens := toolVersionsToTokens(toolVersion) writeLine(&output, encodeLine(newTokens, "")) } } return output.String() } // FindToolVersions looks up a tool version in a tool versions file and if found // returns a slice of versions for it. func FindToolVersions(filepath, toolName string) (versions []string, found bool, err error) { content, err := os.ReadFile(filepath) if err != nil { return versions, false, err } versions, found = findToolVersionsInContent(string(content), toolName) return versions, found, nil } // GetAllToolsAndVersions returns a list of all tools and associated versions // contained in a .tool-versions file func GetAllToolsAndVersions(filepath string) (toolVersions []ToolVersions, err error) { content, err := os.ReadFile(filepath) if err != nil { return toolVersions, err } toolVersions = getAllToolsAndVersionsInContent(string(content)) return toolVersions, nil } // Intersect takes two slices of versions and returns a new slice containing // only the versions found in both. func Intersect(versions1 []string, versions2 []string) (versions []string) { for _, version1 := range versions1 { if slices.Contains(versions2, version1) { versions = append(versions, version1) } } return versions } // Unique takes a slice of ToolVersions and returns a slice of unique tools and // versions. func Unique(versions []ToolVersions) (uniques []ToolVersions) { for _, version := range versions { index := slices.IndexFunc( uniques, func(v ToolVersions) bool { return v.Name == version.Name }, ) if index < 0 { uniques = append(uniques, version) continue } unique := &uniques[index] for _, versionNumber := range version.Versions { if !slices.Contains(unique.Versions, versionNumber) { unique.Versions = append(unique.Versions, versionNumber) } } } return uniques } // ParseFromCliArg parses a string that is passed in as an argument to one of // the asdf subcommands. Some subcommands allow the special version `latest` to // be used, with an optional filter string. func ParseFromCliArg(version string) Version { segments := strings.Split(version, ":") if len(segments) > 0 && segments[0] == "latest" { if len(segments) > 1 { // Must be latest with filter return Version{Type: "latest", Value: segments[1]} } return Version{Type: "latest", Value: ""} } return Parse(version) } // Parse parses a version string into versionType and version components func Parse(version string) Version { segments := strings.Split(version, ":") if len(segments) >= 1 { remainder := strings.Join(segments[1:], ":") switch segments[0] { case "ref": return Version{Type: "ref", Value: remainder} case "path": // This is for people who have the local source already compiled // Like those who work on the language, etc // We'll allow specifying path:/foo/bar/project in .tool-versions // And then use the binaries there return Version{Type: "path", Value: remainder} default: } } if version == "system" { return Version{Type: "system"} } return Version{Type: "version", Value: version} } // ParseSlice takes a slice of strings and returns a slice of parsed versions. func ParseSlice(versions []string) (parsedVersions []Version) { for _, version := range versions { parsedVersions = append(parsedVersions, Parse(version)) } return parsedVersions } // Format takes a Version struct and formats it as a string func Format(version Version) string { switch version.Type { case "system": return "system" case "path": return fmt.Sprintf("path:%s", version.Value) case "ref": return fmt.Sprintf("ref:%s", version.Value) default: return version.Value } } // FormatForFS takes a versionType and version strings and generate a version // string suitable for the file system func FormatForFS(version Version) string { switch version.Type { case "ref": return fmt.Sprintf("ref-%s", version.Value) default: return version.Value } } // VersionStringFromFSFormat takes a version string from directory name and // formats it as version string that can be parsed by the Parse function. func VersionStringFromFSFormat(version string) string { if strings.HasPrefix(version, "ref-") { return strings.Replace(version, "ref-", "ref:", 1) } return version } func readLines(content string) (lines []string) { return strings.Split(content, "\n") } func findToolVersionsInContent(content, toolName string) (versions []string, found bool) { toolVersions := getAllToolsAndVersionsInContent(content) for _, tool := range toolVersions { if tool.Name == toolName { return tool.Versions, true } } return versions, found } func getAllToolsAndVersionsInContent(content string) (toolVersions []ToolVersions) { for _, line := range readLines(content) { tokens, _ := parseLine(line) if len(tokens) > 1 { newTool := ToolVersions{Name: tokens[0], Versions: tokens[1:]} toolVersions = append(toolVersions, newTool) } } return toolVersions } // parseLine receives a single line from a file and parses it into a list of // tokens and a comment. A comment may occur anywhere on the line and is started // by a `#` character. func parseLine(line string) (tokens []string, comment string) { preComment, comment, _ := strings.Cut(line, "#") for _, token := range strings.Split(preComment, " ") { token = strings.TrimSpace(token) if len(token) > 0 { tokens = append(tokens, token) } } return tokens, comment } func toolVersionsToTokens(tv ToolVersions) []string { return append([]string{tv.Name}, tv.Versions...) } func encodeLine(tokens []string, comment string) string { tokensStr := strings.Join(tokens, " ") if comment == "" { if len(tokens) == 0 { return "" } return tokensStr } return fmt.Sprintf("%s #%s", tokensStr, comment) } func writeLine(output *strings.Builder, line string) { if strings.TrimSpace(line) != "" { output.WriteString(line + "\n") } } ================================================ FILE: internal/toolversions/toolversions_test.go ================================================ package toolversions import ( "os" "path/filepath" "testing" "github.com/stretchr/testify/assert" ) func TestGetAllToolsAndVersions(t *testing.T) { t.Run("returns error when non-existent file", func(t *testing.T) { toolVersions, err := GetAllToolsAndVersions("non-existent-file") assert.Error(t, err) assert.Empty(t, toolVersions) }) t.Run("returns list of tool versions when populated file", func(t *testing.T) { toolVersionsPath := filepath.Join(t.TempDir(), ".tool-versions") file, err := os.Create(toolVersionsPath) assert.Nil(t, err) defer file.Close() file.WriteString("ruby 2.0.0") toolVersions, err := GetAllToolsAndVersions(toolVersionsPath) assert.Nil(t, err) expected := []ToolVersions{{Name: "ruby", Versions: []string{"2.0.0"}}} assert.Equal(t, expected, toolVersions) }) } func TestFindToolVersions(t *testing.T) { t.Run("returns error when non-existent file", func(t *testing.T) { versions, found, err := FindToolVersions("non-existent-file", "nonexistent-tool") assert.Error(t, err) assert.False(t, found) assert.Empty(t, versions) }) t.Run("returns list of versions and found true when file contains tool versions", func(t *testing.T) { toolVersionsPath := filepath.Join(t.TempDir(), ".tool-versions") file, err := os.Create(toolVersionsPath) assert.Nil(t, err) defer file.Close() file.WriteString("ruby 2.0.0") versions, found, err := FindToolVersions(toolVersionsPath, "ruby") assert.Nil(t, err) assert.True(t, found) assert.Equal(t, []string{"2.0.0"}, versions) }) } func TestWriteToolVersionsToFile(t *testing.T) { toolVersions := ToolVersions{Name: "lua", Versions: []string{"1.2.3"}} t.Run("writes new file when it does not exist", func(t *testing.T) { path := filepath.Join(t.TempDir(), ".tool-versions") assert.Nil(t, WriteToolVersionsToFile(path, []ToolVersions{toolVersions})) fileContents, err := os.ReadFile(path) assert.Nil(t, err) assert.Equal(t, string(fileContents), "lua 1.2.3\n") }) t.Run("writes new line to end of file when version not already set", func(t *testing.T) { path := filepath.Join(t.TempDir(), ".tool-versions") assert.Nil(t, os.WriteFile(path, []byte("test 1.2.3"), 0o666)) assert.Nil(t, WriteToolVersionsToFile(path, []ToolVersions{toolVersions})) fileContents, err := os.ReadFile(path) assert.Nil(t, err) assert.Equal(t, string(fileContents), "test 1.2.3\nlua 1.2.3\n") }) t.Run("updates existing line when tool already has one or more versions set", func(t *testing.T) { path := filepath.Join(t.TempDir(), ".tool-versions") assert.Nil(t, os.WriteFile(path, []byte("lua 1.1.1"), 0o666)) assert.Nil(t, WriteToolVersionsToFile(path, []ToolVersions{toolVersions})) fileContents, err := os.ReadFile(path) assert.Nil(t, err) assert.Equal(t, string(fileContents), "lua 1.2.3\n") }) } func TestUpdateContentWithToolVersions(t *testing.T) { tests := []struct { desc string input string toolVersions []ToolVersions output string }{ { desc: "returns content unchanged when identical tool and version already set", input: "foobar 1.2.3", toolVersions: []ToolVersions{{Name: "foobar", Versions: []string{"1.2.3"}}}, output: "foobar 1.2.3\n", }, { desc: "writes new line to end of file when version not already set", input: "foobar 1.2.3", toolVersions: []ToolVersions{{Name: "test", Versions: []string{"4.5.6"}}}, output: "foobar 1.2.3\ntest 4.5.6\n", }, { desc: "preserves comments on all other lines", input: "foobar 1.2.3\n# this is a test", toolVersions: []ToolVersions{{Name: "foobar", Versions: []string{"4.5.6"}}}, output: "foobar 4.5.6\n# this is a test\n", }, { desc: "preserves comment on end of the line specifying previous version", input: "foobar 1.2.3 # this is a test", toolVersions: []ToolVersions{{Name: "foobar", Versions: []string{"4.5.6"}}}, output: "foobar 4.5.6 # this is a test\n", }, { desc: "writes multiple versions for same tool", input: "foobar 1.2.3", toolVersions: []ToolVersions{{Name: "foobar", Versions: []string{"4.5.6", "1.2.3"}}}, output: "foobar 4.5.6 1.2.3\n", }, { desc: "writes multiple tools", input: "foobar 1.2.3", toolVersions: []ToolVersions{ {Name: "ruby", Versions: []string{"4.5.6", "1.2.3"}}, {Name: "lua", Versions: []string{"5.2.3"}}, }, output: "foobar 1.2.3\nruby 4.5.6 1.2.3\nlua 5.2.3\n", }, { desc: "writes new version when empty string", input: "", toolVersions: []ToolVersions{{Name: "foobar", Versions: []string{"1.2.3"}}}, output: "foobar 1.2.3\n", }, { desc: "writes new version when empty string", input: "# this is a test", toolVersions: []ToolVersions{{Name: "foobar", Versions: []string{"1.2.3"}}}, output: "# this is a test\nfoobar 1.2.3\n", }, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { output := updateContentWithToolVersions(tt.input, tt.toolVersions) assert.Equal(t, tt.output, output) }) } } func TestIntersect(t *testing.T) { tests := []struct { desc string input1 []string input2 []string want []string }{ { desc: "when provided two empty ToolVersions returns empty ToolVersions", input1: []string{}, input2: []string{}, want: []string(nil), }, { desc: "when provided ToolVersions with no matching versions return empty ToolVersions", input1: []string{"1", "2"}, input2: []string{"3", "4"}, want: []string(nil), }, { desc: "when provided ToolVersions with different versions return new ToolVersions only containing versions in both", input1: []string{"1", "2"}, input2: []string{"2", "3"}, want: []string{"2"}, }, { desc: "preserves order of items in first argument", input1: []string{"1", "3", "2"}, input2: []string{"2", "3"}, want: []string{"3", "2"}, }, { desc: "preserves order of items in first argument (variant 2)", input1: []string{"1", "4", "5", "3", "2"}, input2: []string{"2", "3", "4"}, want: []string{"4", "3", "2"}, }, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { got := Intersect(tt.input1, tt.input2) assert.Equal(t, tt.want, got) }) } } func TestUnique(t *testing.T) { t.Run("returns unique slice of tool versions when tool appears multiple times in slice", func(t *testing.T) { got := Unique([]ToolVersions{ {Name: "foo", Versions: []string{"1"}}, {Name: "foo", Versions: []string{"2"}}, }) want := []ToolVersions{ {Name: "foo", Versions: []string{"1", "2"}}, } assert.Equal(t, got, want) }) t.Run("returns unique slice of tool versions when given slice with multiple tools", func(t *testing.T) { got := Unique([]ToolVersions{ {Name: "foo", Versions: []string{"1"}}, {Name: "bar", Versions: []string{"2"}}, {Name: "foo", Versions: []string{"2"}}, {Name: "bar", Versions: []string{"2"}}, }) want := []ToolVersions{ {Name: "foo", Versions: []string{"1", "2"}}, {Name: "bar", Versions: []string{"2"}}, } assert.Equal(t, got, want) }) } func TestFindToolVersionsInContent(t *testing.T) { t.Run("returns empty list with found false when empty content", func(t *testing.T) { versions, found := findToolVersionsInContent("", "ruby") assert.False(t, found) assert.Empty(t, versions) }) t.Run("returns empty list with found false when tool not found", func(t *testing.T) { versions, found := findToolVersionsInContent("lua 5.4.5", "ruby") assert.False(t, found) assert.Empty(t, versions) }) t.Run("returns list of versions with found true when tool found", func(t *testing.T) { versions, found := findToolVersionsInContent("lua 5.4.5 5.4.6\nruby 2.0.0", "lua") assert.True(t, found) assert.Equal(t, []string{"5.4.5", "5.4.6"}, versions) }) } func TestGetAllToolsAndVersionsInContent(t *testing.T) { tests := []struct { desc string input string want []ToolVersions }{ { desc: "returns empty list with found true and no error when empty content", input: "", want: []ToolVersions(nil), }, { desc: "returns list with one tool when single tool in content", input: "lua 5.4.5 5.4.6", want: []ToolVersions{{Name: "lua", Versions: []string{"5.4.5", "5.4.6"}}}, }, { desc: "returns list with multiple tools when multiple tools in content", input: "lua 5.4.5 5.4.6\nruby 2.0.0", want: []ToolVersions{ {Name: "lua", Versions: []string{"5.4.5", "5.4.6"}}, {Name: "ruby", Versions: []string{"2.0.0"}}, }, }, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { toolsAndVersions := getAllToolsAndVersionsInContent(tt.input) if len(tt.want) == 0 { assert.Empty(t, toolsAndVersions) return } assert.Equal(t, tt.want, toolsAndVersions) }) } } func TestParse(t *testing.T) { t.Run("when passed version string returns struct with type of 'version' and version as value", func(t *testing.T) { version := Parse("1.2.3") assert.Equal(t, version.Type, "version") assert.Equal(t, version.Value, "1.2.3") }) t.Run("when passed ref and version returns struct with type of 'ref' and version as value", func(t *testing.T) { version := Parse("ref:abc123") assert.Equal(t, version.Type, "ref") assert.Equal(t, version.Value, "abc123") }) t.Run("when passed 'ref:' returns struct with type of 'ref' and empty value", func(t *testing.T) { version := Parse("ref:") assert.Equal(t, version.Type, "ref") assert.Equal(t, version.Value, "") }) t.Run("when passed 'system' returns struct with type of 'system'", func(t *testing.T) { version := Parse("system") assert.Equal(t, version.Type, "system") assert.Equal(t, version.Value, "") }) } func TestParseFromCliArg(t *testing.T) { t.Run("when passed 'latest' returns struct with type of 'latest'", func(t *testing.T) { version := ParseFromCliArg("latest") assert.Equal(t, version.Type, "latest") assert.Equal(t, version.Value, "") }) t.Run("when passed latest with filter returns struct with type of 'latest' and unmodified filter string as value", func(t *testing.T) { version := ParseFromCliArg("latest:1.2") assert.Equal(t, version.Type, "latest") assert.Equal(t, version.Value, "1.2") }) t.Run("when passed version string returns struct with type of 'version' and version as value", func(t *testing.T) { version := ParseFromCliArg("1.2.3") assert.Equal(t, version.Type, "version") assert.Equal(t, version.Value, "1.2.3") }) t.Run("when passed ref and version returns struct with type of 'ref' and version as value", func(t *testing.T) { version := ParseFromCliArg("ref:abc123") assert.Equal(t, version.Type, "ref") assert.Equal(t, version.Value, "abc123") }) t.Run("when passed 'ref:' returns struct with type of 'ref' and empty value", func(t *testing.T) { version := ParseFromCliArg("ref:") assert.Equal(t, version.Type, "ref") assert.Equal(t, version.Value, "") }) t.Run("when passed 'system' returns struct with type of 'system'", func(t *testing.T) { version := ParseFromCliArg("system") assert.Equal(t, version.Type, "system") assert.Equal(t, version.Value, "") }) } func TestParseSlice(t *testing.T) { t.Run("returns slice of parsed tool versions", func(t *testing.T) { versions := ParseSlice([]string{"1.2.3"}) assert.Equal(t, []Version{{Type: "version", Value: "1.2.3"}}, versions) }) t.Run("returns empty slice when empty slice provided", func(t *testing.T) { versions := ParseSlice([]string{}) assert.Empty(t, versions) }) t.Run("parses special versions", func(t *testing.T) { versions := ParseSlice([]string{"ref:foo", "system", "path:/foo/bar"}) assert.Equal(t, []Version{{Type: "ref", Value: "foo"}, {Type: "system"}, {Type: "path", Value: "/foo/bar"}}, versions) }) } func TestFormat(t *testing.T) { tests := []struct { desc string input Version output string }{ { desc: "with regular version", input: Version{Type: "version", Value: "foobar"}, output: "foobar", }, { desc: "with ref version", input: Version{Type: "ref", Value: "foobar"}, output: "ref:foobar", }, { desc: "with system version", input: Version{Type: "system", Value: "system"}, output: "system", }, { desc: "with system version", input: Version{Type: "path", Value: "/foo/bar"}, output: "path:/foo/bar", }, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { got := Format(tt.input) assert.Equal(t, got, tt.output) }) } } func TestFormatForFS(t *testing.T) { t.Run("returns version when version type is not ref", func(t *testing.T) { assert.Equal(t, FormatForFS(Version{Type: "version", Value: "foobar"}), "foobar") }) t.Run("returns version prefixed with 'ref-' when version type is ref", func(t *testing.T) { assert.Equal(t, FormatForFS(Version{Type: "ref", Value: "foobar"}), "ref-foobar") }) } func TestVersionStringFromFSFormat(t *testing.T) { tests := []struct { desc string input Version }{ { desc: "with regular version", input: Version{Type: "version", Value: "foobar"}, }, { desc: "with ref version", input: Version{Type: "ref", Value: "foobar"}, }, } for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { got := Parse(VersionStringFromFSFormat(FormatForFS(tt.input))) assert.Equal(t, tt.input, got) }) } } func BenchmarkUnique(b *testing.B) { versions := []ToolVersions{ {Name: "foo", Versions: []string{"1"}}, {Name: "bar", Versions: []string{"2"}}, {Name: "foo", Versions: []string{"2"}}, {Name: "bar", Versions: []string{"2"}}, } for i := 0; i < b.N; i++ { Unique(versions) } } ================================================ FILE: internal/versions/testdata/asdfrc ================================================ pre_asdf_download_testlua = echo pre_asdf_download_lua $@ pre_asdf_install_testlua = echo pre_asdf_install_lua $@ post_asdf_install_testlua = echo post_asdf_install_lua $@ always_keep_download = yes ================================================ FILE: internal/versions/testdata/list-all-elixir ================================================ 0.12.4 0.12.5 0.13.0 0.13.1 0.13.2 0.13.3 0.14.0 0.14.1 0.14.2 0.14.3 0.15.0 0.15.1 1.0.0 1.0.0-otp-17 1.0.0-rc1 1.0.0-rc1-otp-17 1.0.0-rc2 1.0.0-rc2-otp-17 1.0.1 1.0.1-otp-17 1.0.2 1.0.2-otp-17 1.0.3 1.0.3-otp-17 1.0.4 1.0.4-otp-17 1.0.5 1.0.5-otp-17 1.0.5-otp-18 1.1.0 1.1.0-otp-17 1.1.0-otp-18 1.1.0-rc.0 1.1.0-rc.0-otp-17 1.1.0-rc.0-otp-18 1.1.1 1.1.1-otp-17 1.1.1-otp-18 1.2.0 1.2.0-otp-18 1.2.0-rc.0 1.2.0-rc.0-otp-18 1.2.0-rc.1 1.2.0-rc.1-otp-18 1.2.1 1.2.1-otp-18 1.2.2 1.2.2-otp-18 1.2.3 1.2.3-otp-18 1.2.4 1.2.4-otp-18 1.2.5 1.2.5-otp-18 1.2.6 1.2.6-otp-18 1.2.6-otp-19 1.3.0 1.3.0-otp-18 1.3.0-otp-19 1.3.0-rc.0 1.3.0-rc.0-otp-18 1.3.0-rc.0-otp-19 1.3.0-rc.1 1.3.0-rc.1-otp-18 1.3.0-rc.1-otp-19 1.3.1 1.3.1-otp-18 1.3.1-otp-19 1.3.2 1.3.2-otp-18 1.3.2-otp-19 1.3.3 1.3.3-otp-18 1.3.3-otp-19 1.3.4 1.3.4-otp-18 1.3.4-otp-19 1.4.0 1.4.0-otp-18 1.4.0-otp-19 1.4.0-rc.0 1.4.0-rc.0-otp-18 1.4.0-rc.0-otp-19 1.4.0-rc.0-otp-20 1.4.0-rc.1 1.4.0-rc.1-otp-18 1.4.0-rc.1-otp-19 1.4.0-rc.1-otp-20 1.4.1 1.4.1-otp-18 1.4.1-otp-19 1.4.2 1.4.2-otp-18 1.4.2-otp-19 1.4.3 1.4.3-otp-18 1.4.3-otp-19 1.4.4 1.4.4-otp-18 1.4.4-otp-19 1.4.5 1.4.5-otp-18 1.4.5-otp-19 1.4.5-otp-20 1.5.0 1.5.0-otp-18 1.5.0-otp-19 1.5.0-otp-20 1.5.0-rc.0 1.5.0-rc.0-otp-18 1.5.0-rc.0-otp-19 1.5.0-rc.0-otp-20 1.5.0-rc.1 1.5.0-rc.1-otp-18 1.5.0-rc.1-otp-19 1.5.0-rc.1-otp-20 1.5.0-rc.2 1.5.0-rc.2-otp-18 1.5.0-rc.2-otp-19 1.5.0-rc.2-otp-20 1.5.1 1.5.1-otp-18 1.5.1-otp-19 1.5.1-otp-20 1.5.2 1.5.2-otp-18 1.5.2-otp-19 1.5.2-otp-20 1.5.3 1.5.3-otp-18 1.5.3-otp-19 1.5.3-otp-20 1.6.0 1.6.0-otp-19 1.6.0-otp-20 1.6.0-rc.0 1.6.0-rc.0-otp-19 1.6.0-rc.0-otp-20 1.6.0-rc.1 1.6.0-rc.1-otp-19 1.6.0-rc.1-otp-20 1.6.1 1.6.1-otp-19 1.6.1-otp-20 1.6.2 1.6.2-otp-19 1.6.2-otp-20 1.6.3 1.6.3-otp-19 1.6.3-otp-20 1.6.4 1.6.4-otp-19 1.6.4-otp-20 1.6.5 1.6.5-otp-19 1.6.5-otp-20 1.6.5-otp-21 1.6.6 1.6.6-otp-19 1.6.6-otp-20 1.6.6-otp-21 1.7.0 1.7.0-otp-19 1.7.0-otp-20 1.7.0-otp-21 1.7.0-otp-22 1.7.0-rc.0 1.7.0-rc.0-otp-19 1.7.0-rc.0-otp-20 1.7.0-rc.0-otp-21 1.7.0-rc.0-otp-22 1.7.0-rc.1 1.7.0-rc.1-otp-19 1.7.0-rc.1-otp-20 1.7.0-rc.1-otp-21 1.7.0-rc.1-otp-22 1.7.1 1.7.1-otp-19 1.7.1-otp-20 1.7.1-otp-21 1.7.1-otp-22 1.7.2 1.7.2-otp-19 1.7.2-otp-20 1.7.2-otp-21 1.7.2-otp-22 1.7.3 1.7.3-otp-19 1.7.3-otp-20 1.7.3-otp-21 1.7.3-otp-22 1.7.4 1.7.4-otp-19 1.7.4-otp-20 1.7.4-otp-21 1.7.4-otp-22 1.8.0 1.8.0-otp-20 1.8.0-otp-21 1.8.0-otp-22 1.8.0-rc.0 1.8.0-rc.0-otp-20 1.8.0-rc.0-otp-21 1.8.0-rc.0-otp-22 1.8.0-rc.1 1.8.0-rc.1-otp-20 1.8.0-rc.1-otp-21 1.8.0-rc.1-otp-22 1.8.1 1.8.1-otp-20 1.8.1-otp-21 1.8.1-otp-22 1.8.2 1.8.2-otp-20 1.8.2-otp-21 1.8.2-otp-22 1.9.0 1.9.0-otp-20 1.9.0-otp-21 1.9.0-otp-22 1.9.0-rc.0 1.9.0-rc.0-otp-20 1.9.0-rc.0-otp-21 1.9.0-rc.0-otp-22 1.9.1 1.9.1-otp-20 1.9.1-otp-21 1.9.1-otp-22 1.9.2 1.9.2-otp-20 1.9.2-otp-21 1.9.2-otp-22 1.9.3 1.9.3-otp-20 1.9.3-otp-21 1.9.3-otp-22 1.9.4 1.9.4-otp-20 1.9.4-otp-21 1.9.4-otp-22 1.10.0 1.10.0-otp-21 1.10.0-otp-22 1.10.0-rc.0 1.10.0-rc.0-otp-21 1.10.0-rc.0-otp-22 1.10.1 1.10.1-otp-21 1.10.1-otp-22 1.10.2 1.10.2-otp-21 1.10.2-otp-22 1.10.3 1.10.3-otp-21 1.10.3-otp-22 1.10.3-otp-23 1.10.4 1.10.4-otp-21 1.10.4-otp-22 1.10.4-otp-23 1.11.0 1.11.0-otp-21 1.11.0-otp-22 1.11.0-otp-23 1.11.0-rc.0 1.11.0-rc.0-otp-21 1.11.0-rc.0-otp-22 1.11.0-rc.0-otp-23 1.11.1 1.11.1-otp-21 1.11.1-otp-22 1.11.1-otp-23 1.11.2 1.11.2-otp-21 1.11.2-otp-22 1.11.2-otp-23 1.11.3 1.11.3-otp-21 1.11.3-otp-22 1.11.3-otp-23 1.11.4 1.11.4-otp-21 1.11.4-otp-22 1.11.4-otp-23 1.11.4-otp-24 1.12.0 1.12.0-otp-22 1.12.0-otp-23 1.12.0-otp-24 1.12.0-rc.0 1.12.0-rc.0-otp-21 1.12.0-rc.0-otp-22 1.12.0-rc.0-otp-23 1.12.0-rc.0-otp-24 1.12.0-rc.1 1.12.0-rc.1-otp-22 1.12.0-rc.1-otp-23 1.12.0-rc.1-otp-24 1.12.1 1.12.1-otp-22 1.12.1-otp-23 1.12.1-otp-24 1.12.2 1.12.2-otp-22 1.12.2-otp-23 1.12.2-otp-24 1.12.3 1.12.3-otp-22 1.12.3-otp-23 1.12.3-otp-24 1.13.0 1.13.0-otp-22 1.13.0-otp-23 1.13.0-otp-24 1.13.0-otp-25 1.13.0-rc.0 1.13.0-rc.0-otp-22 1.13.0-rc.0-otp-23 1.13.0-rc.0-otp-24 1.13.0-rc.0-otp-25 1.13.0-rc.1 1.13.0-rc.1-otp-22 1.13.0-rc.1-otp-23 1.13.0-rc.1-otp-24 1.13.0-rc.1-otp-25 1.13.1 1.13.1-otp-22 1.13.1-otp-23 1.13.1-otp-24 1.13.1-otp-25 1.13.2 1.13.2-otp-22 1.13.2-otp-23 1.13.2-otp-24 1.13.2-otp-25 1.13.3 1.13.3-otp-22 1.13.3-otp-23 1.13.3-otp-24 1.13.3-otp-25 1.13.4 1.13.4-otp-22 1.13.4-otp-23 1.13.4-otp-24 1.13.4-otp-25 1.14.0 1.14.0-otp-23 1.14.0-otp-24 1.14.0-otp-25 1.14.0-rc.0 1.14.0-rc.0-otp-23 1.14.0-rc.0-otp-24 1.14.0-rc.0-otp-25 1.14.0-rc.1 1.14.0-rc.1-otp-23 1.14.0-rc.1-otp-24 1.14.0-rc.1-otp-25 1.14.1 1.14.1-otp-23 1.14.1-otp-24 1.14.1-otp-25 1.14.2 1.14.2-otp-23 1.14.2-otp-24 1.14.2-otp-25 1.14.3 1.14.3-otp-23 1.14.3-otp-24 1.14.3-otp-25 1.14.4 1.14.4-otp-23 1.14.4-otp-24 1.14.4-otp-25 1.14.4-otp-26 1.14.5 1.14.5-otp-23 1.14.5-otp-24 1.14.5-otp-25 1.14.5-otp-26 1.15.0 1.15.0-otp-24 1.15.0-otp-25 1.15.0-otp-26 1.15.0-rc.0 1.15.0-rc.0-otp-24 1.15.0-rc.0-otp-25 1.15.0-rc.0-otp-26 1.15.0-rc.1 1.15.0-rc.1-otp-24 1.15.0-rc.1-otp-25 1.15.0-rc.1-otp-26 1.15.0-rc.2 1.15.0-rc.2-otp-24 1.15.0-rc.2-otp-25 1.15.0-rc.2-otp-26 1.15.1 1.15.1-otp-24 1.15.1-otp-25 1.15.1-otp-26 1.15.2 1.15.2-otp-24 1.15.2-otp-25 1.15.2-otp-26 1.15.3 1.15.3-otp-24 1.15.3-otp-25 1.15.3-otp-26 1.15.4 1.15.4-otp-24 1.15.4-otp-25 1.15.4-otp-26 1.15.5 1.15.5-otp-24 1.15.5-otp-25 1.15.5-otp-26 1.15.6 1.15.6-otp-24 1.15.6-otp-25 1.15.6-otp-26 1.15.7 1.15.7-otp-24 1.15.7-otp-25 1.15.7-otp-26 1.15.8 1.15.8-otp-24 1.15.8-otp-25 1.15.8-otp-26 1.16.0 1.16.0-otp-24 1.16.0-otp-25 1.16.0-otp-26 1.16.0-rc.0 1.16.0-rc.0-otp-24 1.16.0-rc.0-otp-25 1.16.0-rc.0-otp-26 1.16.0-rc.1 1.16.0-rc.1-otp-24 1.16.0-rc.1-otp-25 1.16.0-rc.1-otp-26 1.16.1 1.16.1-otp-24 1.16.1-otp-25 1.16.1-otp-26 1.16.2 1.16.2-otp-24 1.16.2-otp-25 1.16.2-otp-26 1.16.3 1.16.3-otp-24 1.16.3-otp-25 1.16.3-otp-26 1.17.0 1.17.0-otp-25 1.17.0-otp-26 1.17.0-otp-27 1.17.0-rc.0 1.17.0-rc.0-otp-25 1.17.0-rc.0-otp-26 1.17.0-rc.0-otp-27 1.17.0-rc.1 1.17.0-rc.1-otp-25 1.17.0-rc.1-otp-26 1.17.0-rc.1-otp-27 1.17.1 1.17.1-otp-25 1.17.1-otp-26 1.17.1-otp-27 1.17.2 1.17.2-otp-25 1.17.2-otp-26 1.17.2-otp-27 1.17.3 1.17.3-otp-25 1.17.3-otp-26 1.17.3-otp-27 1.18.0 1.18.0-otp-25 1.18.0-otp-26 1.18.0-otp-27 1.18.0-rc.0 1.18.0-rc.0-otp-25 1.18.0-rc.0-otp-26 1.18.0-rc.0-otp-27 1.18.1 1.18.1-otp-25 1.18.1-otp-26 1.18.1-otp-27 1.18.2 1.18.2-otp-25 1.18.2-otp-26 1.18.2-otp-27 main main-otp-22 main-otp-23 main-otp-24 main-otp-25 main-otp-26 main-otp-27 master master-otp-21 master-otp-22 master-otp-23 master-otp-24 ================================================ FILE: internal/versions/testdata/list-all-python ================================================ 2.1.3 2.2.3 2.3.7 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.6.5 2.6.6 2.6.7 2.6.8 2.6.9 2.7.0 2.7-dev 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.7.10 2.7.11 2.7.12 2.7.13 2.7.14 2.7.15 2.7.16 2.7.17 2.7.18 3.0.1 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.2.0 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.4.0 3.4-dev 3.4.1 3.4.2 3.4.3 3.4.4 3.4.5 3.4.6 3.4.7 3.4.8 3.4.9 3.4.10 3.5.0 3.5-dev 3.5.1 3.5.2 3.5.3 3.5.4 3.5.5 3.5.6 3.5.7 3.5.8 3.5.9 3.5.10 3.6.0 3.6-dev 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.6.9 3.6.10 3.6.11 3.6.12 3.6.13 3.6.14 3.6.15 3.7.0 3.7-dev 3.7.1 3.7.2 3.7.3 3.7.4 3.7.5 3.7.6 3.7.7 3.7.8 3.7.9 3.7.10 3.7.11 3.7.12 3.7.13 3.7.14 3.7.15 3.7.16 3.7.17 3.8.0 3.8-dev 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.8.8 3.8.9 3.8.10 3.8.11 3.8.12 3.8.13 3.8.14 3.8.15 3.8.16 3.8.17 3.8.18 3.8.19 3.8.20 3.9.0 3.9-dev 3.9.1 3.9.2 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9 3.9.10 3.9.11 3.9.12 3.9.13 3.9.14 3.9.15 3.9.16 3.9.17 3.9.18 3.9.19 3.9.20 3.9.21 3.10.0 3.10-dev 3.10.1 3.10.2 3.10.3 3.10.4 3.10.5 3.10.6 3.10.7 3.10.8 3.10.9 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.16 3.11.0 3.11-dev 3.11.1 3.11.2 3.11.3 3.11.4 3.11.5 3.11.6 3.11.7 3.11.8 3.11.9 3.11.10 3.11.11 3.12.0 3.12-dev 3.12.1 3.12.2 3.12.3 3.12.4 3.12.5 3.12.6 3.12.7 3.12.8 3.12.9 3.13.0 3.13.0t 3.13-dev 3.13t-dev 3.13.1 3.13.1t 3.13.2 3.13.2t 3.14.0a5 3.14.0a5t 3.14-dev 3.14t-dev activepython-2.7.14 activepython-3.5.4 activepython-3.6.0 anaconda-1.4.0 anaconda-1.5.0 anaconda-1.5.1 anaconda-1.6.0 anaconda-1.6.1 anaconda-1.7.0 anaconda-1.8.0 anaconda-1.9.0 anaconda-1.9.1 anaconda-1.9.2 anaconda-2.0.0 anaconda-2.0.1 anaconda-2.1.0 anaconda-2.2.0 anaconda-2.3.0 anaconda-2.4.0 anaconda-4.0.0 anaconda2-2.4.0 anaconda2-2.4.1 anaconda2-2.5.0 anaconda2-4.0.0 anaconda2-4.1.0 anaconda2-4.1.1 anaconda2-4.2.0 anaconda2-4.3.0 anaconda2-4.3.1 anaconda2-4.4.0 anaconda2-5.0.0 anaconda2-5.0.1 anaconda2-5.1.0 anaconda2-5.2.0 anaconda2-5.3.0 anaconda2-5.3.1 anaconda2-2018.12 anaconda2-2019.03 anaconda2-2019.07 anaconda2-2019.10 anaconda3-2.0.0 anaconda3-2.0.1 anaconda3-2.1.0 anaconda3-2.2.0 anaconda3-2.3.0 anaconda3-2.4.0 anaconda3-2.4.1 anaconda3-2.5.0 anaconda3-4.0.0 anaconda3-4.1.0 anaconda3-4.1.1 anaconda3-4.2.0 anaconda3-4.3.0 anaconda3-4.3.1 anaconda3-4.4.0 anaconda3-5.0.0 anaconda3-5.0.1 anaconda3-5.1.0 anaconda3-5.2.0 anaconda3-5.3.0 anaconda3-5.3.1 anaconda3-2018.12 anaconda3-2019.03 anaconda3-2019.07 anaconda3-2019.10 anaconda3-2020.02 anaconda3-2020.07 anaconda3-2020.11 anaconda3-2021.04 anaconda3-2021.05 anaconda3-2021.11 anaconda3-2022.05 anaconda3-2022.10 anaconda3-2023.03-0 anaconda3-2023.03 anaconda3-2023.03-1 anaconda3-2023.07-0 anaconda3-2023.07-1 anaconda3-2023.07-2 anaconda3-2023.09-0 anaconda3-2024.02-1 anaconda3-2024.06-1 anaconda3-2024.10-1 cinder-3.8-dev cinder-3.10-dev graalpy-dev graalpy-community-23.1.0 graalpy-community-23.1.2 graalpy-community-24.0.0 graalpy-community-24.1.0 graalpy-community-24.1.1 graalpy-community-24.1.2 graalpy-22.3.0 graalpy-23.0.0 graalpy-23.1.0 graalpy-23.1.2 graalpy-24.0.0 graalpy-24.1.0 graalpy-24.1.1 graalpy-24.1.2 graalpython-20.1.0 graalpython-20.2.0 graalpython-20.3.0 graalpython-21.0.0 graalpython-21.1.0 graalpython-21.2.0 graalpython-21.3.0 graalpython-22.0.0 graalpython-22.1.0 graalpython-22.2.0 ironpython-dev ironpython-2.7.4 ironpython-2.7.5 ironpython-2.7.6.3 ironpython-2.7.7 jython-dev jython-2.5.0 jython-2.5-dev jython-2.5.1 jython-2.5.2 jython-2.5.3 jython-2.5.4-rc1 jython-2.7.0 jython-2.7.1 jython-2.7.2 jython-2.7.3 mambaforge-pypy3 mambaforge mambaforge-4.10.1-4 mambaforge-4.10.1-5 mambaforge-4.10.2-0 mambaforge-4.10.3-0 mambaforge-4.10.3-1 mambaforge-4.10.3-2 mambaforge-4.10.3-3 mambaforge-4.10.3-4 mambaforge-4.10.3-5 mambaforge-4.10.3-6 mambaforge-4.10.3-7 mambaforge-4.10.3-8 mambaforge-4.10.3-9 mambaforge-4.10.3-10 mambaforge-4.11.0-0 mambaforge-4.11.0-1 mambaforge-4.11.0-2 mambaforge-4.11.0-3 mambaforge-4.11.0-4 mambaforge-4.12.0-0 mambaforge-4.12.0-1 mambaforge-4.12.0-2 mambaforge-4.12.0-3 mambaforge-4.13.0-1 mambaforge-4.14.0-0 mambaforge-4.14.0-1 mambaforge-4.14.0-2 mambaforge-22.9.0-0 mambaforge-22.9.0-1 mambaforge-22.9.0-2 mambaforge-22.9.0-3 mambaforge-22.11.1-3 mambaforge-22.11.1-4 mambaforge-23.1.0-0 mambaforge-23.1.0-1 mambaforge-23.1.0-2 mambaforge-23.1.0-3 mambaforge-23.1.0-4 mambaforge-23.3.0-0 mambaforge-23.3.1-0 mambaforge-23.3.1-1 mambaforge-23.10.0-0 mambaforge-23.11.0-0 mambaforge-24.1.2-0 mambaforge-24.3.0-0 mambaforge-24.5.0-0 mambaforge-24.7.1-0 mambaforge-24.7.1-1 mambaforge-24.7.1-2 mambaforge-24.9.0-0 mambaforge-24.9.2-0 mambaforge-24.11.0-0 mambaforge-24.11.0-1 micropython-dev micropython-1.9.3 micropython-1.9.4 micropython-1.10 micropython-1.11 micropython-1.12 micropython-1.13 micropython-1.14 micropython-1.15 micropython-1.16 micropython-1.17 micropython-1.18 micropython-1.19.1 micropython-1.20.0 micropython-1.21.0 miniconda-latest miniconda-2.2.2 miniconda-3.0.0 miniconda-3.0.4 miniconda-3.0.5 miniconda-3.3.0 miniconda-3.4.2 miniconda-3.7.0 miniconda-3.8.3 miniconda-3.9.1 miniconda-3.10.1 miniconda-3.16.0 miniconda-3.18.3 miniconda2-latest miniconda2-2.7-4.8.3 miniconda2-3.18.3 miniconda2-3.19.0 miniconda2-4.0.5 miniconda2-4.1.11 miniconda2-4.3.14 miniconda2-4.3.21 miniconda2-4.3.27 miniconda2-4.3.30 miniconda2-4.3.31 miniconda2-4.4.10 miniconda2-4.5.1 miniconda2-4.5.4 miniconda2-4.5.11 miniconda2-4.5.12 miniconda2-4.6.14 miniconda2-4.7.10 miniconda2-4.7.12 miniconda3-latest miniconda3-2.2.2 miniconda3-3.0.0 miniconda3-3.0.4 miniconda3-3.0.5 miniconda3-3.3.0 miniconda3-3.4.2 miniconda3-3.7.0 miniconda3-3.7-4.8.2 miniconda3-3.7-4.8.3 miniconda3-3.7-4.9.2 miniconda3-3.7-4.10.1 miniconda3-3.7-4.10.3 miniconda3-3.7-4.11.0 miniconda3-3.7-4.12.0 miniconda3-3.7-22.11.1-1 miniconda3-3.7-23.1.0-1 miniconda3-3.8.3 miniconda3-3.8-4.8.2 miniconda3-3.8-4.8.3 miniconda3-3.8-4.9.2 miniconda3-3.8-4.10.1 miniconda3-3.8-4.10.3 miniconda3-3.8-4.11.0 miniconda3-3.8-4.12.0 miniconda3-3.8-22.11.1-1 miniconda3-3.8-23.1.0-1 miniconda3-3.8-23.3.1-0 miniconda3-3.8-23.5.0-3 miniconda3-3.8-23.5.1-0 miniconda3-3.8-23.5.2-0 miniconda3-3.8-23.9.0-0 miniconda3-3.8-23.10.0-1 miniconda3-3.8-23.11.0-1 miniconda3-3.8-23.11.0-2 miniconda3-3.9.1 miniconda3-3.9-4.9.2 miniconda3-3.9-4.10.1 miniconda3-3.9-4.10.3 miniconda3-3.9-4.11.0 miniconda3-3.9-4.12.0 miniconda3-3.9-22.11.1-1 miniconda3-3.9-23.1.0-1 miniconda3-3.9-23.3.1-0 miniconda3-3.9-23.5.0-3 miniconda3-3.9-23.5.1-0 miniconda3-3.9-23.5.2-0 miniconda3-3.9-23.9.0-0 miniconda3-3.9-23.10.0-1 miniconda3-3.9-23.11.0-1 miniconda3-3.9-23.11.0-2 miniconda3-3.9-24.1.2-0 miniconda3-3.9-24.3.0-0 miniconda3-3.9-24.4.0-0 miniconda3-3.9-24.5.0-0 miniconda3-3.9-24.7.1-0 miniconda3-3.9-24.9.2-0 miniconda3-3.9-24.11.1-0 miniconda3-3.9-25.1.1-0 miniconda3-3.9-25.1.1-1 miniconda3-3.9-25.1.1-2 miniconda3-3.10.1 miniconda3-3.10-22.11.1-1 miniconda3-3.10-23.1.0-1 miniconda3-3.10-23.3.1-0 miniconda3-3.10-23.5.0-3 miniconda3-3.10-23.5.1-0 miniconda3-3.10-23.5.2-0 miniconda3-3.10-23.9.0-0 miniconda3-3.10-23.10.0-1 miniconda3-3.10-23.11.0-1 miniconda3-3.10-23.11.0-2 miniconda3-3.10-24.1.2-0 miniconda3-3.10-24.3.0-0 miniconda3-3.10-24.4.0-0 miniconda3-3.10-24.5.0-0 miniconda3-3.10-24.7.1-0 miniconda3-3.10-24.9.2-0 miniconda3-3.10-24.11.1-0 miniconda3-3.10-25.1.1-0 miniconda3-3.10-25.1.1-1 miniconda3-3.10-25.1.1-2 miniconda3-3.11-23.5.0-3 miniconda3-3.11-23.5.1-0 miniconda3-3.11-23.5.2-0 miniconda3-3.11-23.9.0-0 miniconda3-3.11-23.10.0-1 miniconda3-3.11-23.11.0-1 miniconda3-3.11-23.11.0-2 miniconda3-3.11-24.1.2-0 miniconda3-3.11-24.3.0-0 miniconda3-3.11-24.4.0-0 miniconda3-3.11-24.5.0-0 miniconda3-3.11-24.7.1-0 miniconda3-3.11-24.9.2-0 miniconda3-3.11-24.11.1-0 miniconda3-3.11-25.1.1-0 miniconda3-3.11-25.1.1-1 miniconda3-3.11-25.1.1-2 miniconda3-3.12-24.1.2-0 miniconda3-3.12-24.3.0-0 miniconda3-3.12-24.4.0-0 miniconda3-3.12-24.5.0-0 miniconda3-3.12-24.7.1-0 miniconda3-3.12-24.9.2-0 miniconda3-3.12-24.11.1-0 miniconda3-3.12-25.1.1-0 miniconda3-3.12-25.1.1-1 miniconda3-3.12-25.1.1-2 miniconda3-3.16.0 miniconda3-3.18.3 miniconda3-3.19.0 miniconda3-4.0.5 miniconda3-4.1.11 miniconda3-4.2.12 miniconda3-4.3.11 miniconda3-4.3.14 miniconda3-4.3.21 miniconda3-4.3.27 miniconda3-4.3.30 miniconda3-4.3.31 miniconda3-4.4.10 miniconda3-4.5.1 miniconda3-4.5.4 miniconda3-4.5.11 miniconda3-4.5.12 miniconda3-4.6.14 miniconda3-4.7.10 miniconda3-4.7.12 miniforge-pypy3 miniforge3-latest miniforge3-4.9.2 miniforge3-4.10 miniforge3-4.10.1-1 miniforge3-4.10.1-3 miniforge3-4.10.1-5 miniforge3-4.10.2-0 miniforge3-4.10.3-0 miniforge3-4.10.3-1 miniforge3-4.10.3-2 miniforge3-4.10.3-3 miniforge3-4.10.3-4 miniforge3-4.10.3-5 miniforge3-4.10.3-6 miniforge3-4.10.3-7 miniforge3-4.10.3-8 miniforge3-4.10.3-9 miniforge3-4.10.3-10 miniforge3-4.11.0-0 miniforge3-4.11.0-1 miniforge3-4.11.0-2 miniforge3-4.11.0-3 miniforge3-4.11.0-4 miniforge3-4.12.0-0 miniforge3-4.12.0-1 miniforge3-4.12.0-2 miniforge3-4.12.0-3 miniforge3-4.13.0-0 miniforge3-4.13.0-1 miniforge3-4.14.0-0 miniforge3-4.14.0-1 miniforge3-4.14.0-2 miniforge3-22.9.0-0 miniforge3-22.9.0-1 miniforge3-22.9.0-2 miniforge3-22.9.0-3 miniforge3-22.11.1-3 miniforge3-22.11.1-4 miniforge3-23.1.0-0 miniforge3-23.1.0-1 miniforge3-23.1.0-2 miniforge3-23.1.0-3 miniforge3-23.1.0-4 miniforge3-23.3.0-0 miniforge3-23.3.1-0 miniforge3-23.3.1-1 miniforge3-23.10.0-0 miniforge3-23.11.0-0 miniforge3-24.1.2-0 miniforge3-24.3.0-0 miniforge3-24.5.0-0 miniforge3-24.7.1-0 miniforge3-24.7.1-1 miniforge3-24.7.1-2 miniforge3-24.9.0-0 miniforge3-24.9.2-0 miniforge3-24.11.0-0 miniforge3-24.11.0-1 miniforge3-24.11.2-0 miniforge3-24.11.2-1 miniforge3-24.11.3-0 miniforge3-25.1.1-0 nogil-3.9.10 nogil-3.9.10-1 pypy-c-jit-latest pypy-dev pypy-stm-2.3 pypy-stm-2.5.1 pypy-1.5-src pypy-1.6 pypy-1.7 pypy-1.8 pypy-1.9 pypy-2.0-src pypy-2.0 pypy-2.0.1-src pypy-2.0.1 pypy-2.0.2-src pypy-2.0.2 pypy-2.1-src pypy-2.1 pypy-2.2-src pypy-2.2 pypy-2.2.1-src pypy-2.2.1 pypy-2.3-src pypy-2.3 pypy-2.3.1-src pypy-2.3.1 pypy-2.4.0-src pypy-2.4.0 pypy-2.5.0-src pypy-2.5.0 pypy-2.5.1-src pypy-2.5.1 pypy-2.6.0-src pypy-2.6.0 pypy-2.6.1-src pypy-2.6.1 pypy-4.0.0-src pypy-4.0.0 pypy-4.0.1-src pypy-4.0.1 pypy-5.0.0-src pypy-5.0.0 pypy-5.0.1-src pypy-5.0.1 pypy-5.1-src pypy-5.1 pypy-5.1.1-src pypy-5.1.1 pypy-5.3-src pypy-5.3 pypy-5.3.1-src pypy-5.3.1 pypy-5.4-src pypy-5.4 pypy-5.4.1-src pypy-5.4.1 pypy-5.6.0-src pypy-5.6.0 pypy-5.7.0-src pypy-5.7.0 pypy-5.7.1-src pypy-5.7.1 pypy2-5.3-src pypy2-5.3 pypy2-5.3.1-src pypy2-5.3.1 pypy2-5.4-src pypy2-5.4 pypy2-5.4.1-src pypy2-5.4.1 pypy2-5.6.0-src pypy2-5.6.0 pypy2-5.7.0-src pypy2-5.7.0 pypy2-5.7.1-src pypy2-5.7.1 pypy2.7-5.8.0-src pypy2.7-5.8.0 pypy2.7-5.9.0-src pypy2.7-5.9.0 pypy2.7-5.10.0-src pypy2.7-5.10.0 pypy2.7-6.0.0-src pypy2.7-6.0.0 pypy2.7-7.0.0-src pypy2.7-7.0.0 pypy2.7-7.1.0-src pypy2.7-7.1.0 pypy2.7-7.1.1-src pypy2.7-7.1.1 pypy2.7-7.2.0-src pypy2.7-7.2.0 pypy2.7-7.3.0-src pypy2.7-7.3.0 pypy2.7-7.3.1-src pypy2.7-7.3.1 pypy2.7-7.3.2-src pypy2.7-7.3.2 pypy2.7-7.3.3-src pypy2.7-7.3.3 pypy2.7-7.3.4-src pypy2.7-7.3.4 pypy2.7-7.3.5-src pypy2.7-7.3.5 pypy2.7-7.3.6-src pypy2.7-7.3.6 pypy2.7-7.3.8-src pypy2.7-7.3.8 pypy2.7-7.3.9-src pypy2.7-7.3.9 pypy2.7-7.3.10-src pypy2.7-7.3.10 pypy2.7-7.3.11-src pypy2.7-7.3.11 pypy2.7-7.3.12-src pypy2.7-7.3.12 pypy2.7-7.3.13-src pypy2.7-7.3.13 pypy2.7-7.3.14-src pypy2.7-7.3.14 pypy2.7-7.3.15-src pypy2.7-7.3.15 pypy2.7-7.3.16-src pypy2.7-7.3.16 pypy2.7-7.3.17-src pypy2.7-7.3.17 pypy2.7-7.3.18-src pypy2.7-7.3.18 pypy3-2.3.1-src pypy3-2.3.1 pypy3-2.4.0-src pypy3-2.4.0 pypy3.3-5.2-alpha1-src pypy3.3-5.2-alpha1 pypy3.3-5.5-alpha-src pypy3.3-5.5-alpha pypy3.5-c-jit-latest pypy3.5-5.7-beta-src pypy3.5-5.7-beta pypy3.5-5.7.1-beta-src pypy3.5-5.7.1-beta pypy3.5-5.8.0-src pypy3.5-5.8.0 pypy3.5-5.9.0-src pypy3.5-5.9.0 pypy3.5-5.10.0-src pypy3.5-5.10.0 pypy3.5-5.10.1-src pypy3.5-5.10.1 pypy3.5-6.0.0-src pypy3.5-6.0.0 pypy3.5-7.0.0-src pypy3.5-7.0.0 pypy3.6-7.0.0-src pypy3.6-7.0.0 pypy3.6-7.1.0-src pypy3.6-7.1.0 pypy3.6-7.1.1-src pypy3.6-7.1.1 pypy3.6-7.2.0-src pypy3.6-7.2.0 pypy3.6-7.3.0-src pypy3.6-7.3.0 pypy3.6-7.3.1-src pypy3.6-7.3.1 pypy3.6-7.3.2-src pypy3.6-7.3.2 pypy3.6-7.3.3-src pypy3.6-7.3.3 pypy3.7-c-jit-latest pypy3.7-7.3.2-src pypy3.7-7.3.2 pypy3.7-7.3.3-src pypy3.7-7.3.3 pypy3.7-7.3.4-src pypy3.7-7.3.4 pypy3.7-7.3.5-src pypy3.7-7.3.5 pypy3.7-7.3.6-src pypy3.7-7.3.6 pypy3.7-7.3.7-src pypy3.7-7.3.7 pypy3.7-7.3.8-src pypy3.7-7.3.8 pypy3.7-7.3.9-src pypy3.7-7.3.9 pypy3.8-7.3.6-src pypy3.8-7.3.6 pypy3.8-7.3.7-src pypy3.8-7.3.7 pypy3.8-7.3.8-src pypy3.8-7.3.8 pypy3.8-7.3.9-src pypy3.8-7.3.9 pypy3.8-7.3.10-src pypy3.8-7.3.10 pypy3.8-7.3.11-src pypy3.8-7.3.11 pypy3.9-7.3.8-src pypy3.9-7.3.8 pypy3.9-7.3.9-src pypy3.9-7.3.9 pypy3.9-7.3.10-src pypy3.9-7.3.10 pypy3.9-7.3.11-src pypy3.9-7.3.11 pypy3.9-7.3.12-src pypy3.9-7.3.12 pypy3.9-7.3.13-src pypy3.9-7.3.13 pypy3.9-7.3.14-src pypy3.9-7.3.14 pypy3.9-7.3.15-src pypy3.9-7.3.15 pypy3.9-7.3.16-src pypy3.9-7.3.16 pypy3.10-7.3.12-src pypy3.10-7.3.12 pypy3.10-7.3.13-src pypy3.10-7.3.13 pypy3.10-7.3.14-src pypy3.10-7.3.14 pypy3.10-7.3.15-src pypy3.10-7.3.15 pypy3.10-7.3.16-src pypy3.10-7.3.16 pypy3.10-7.3.17-src pypy3.10-7.3.17 pypy3.10-7.3.18-src pypy3.10-7.3.18 pypy3.11-7.3.18-src pypy3.11-7.3.18 pyston-2.2 pyston-2.3 pyston-2.3.1 pyston-2.3.2 pyston-2.3.3 pyston-2.3.4 pyston-2.3.5 stackless-dev stackless-2.7-dev stackless-2.7.2 stackless-2.7.3 stackless-2.7.4 stackless-2.7.5 stackless-2.7.6 stackless-2.7.7 stackless-2.7.8 stackless-2.7.9 stackless-2.7.10 stackless-2.7.11 stackless-2.7.12 stackless-2.7.14 stackless-2.7.16 stackless-3.2.2 stackless-3.2.5 stackless-3.3.5 stackless-3.3.7 stackless-3.4-dev stackless-3.4.2 stackless-3.4.7 stackless-3.5.4 stackless-3.7.5 ================================================ FILE: internal/versions/testdata/list-all-ruby ================================================ 1.8.5-p52 1.8.5-p113 1.8.5-p114 1.8.5-p115 1.8.5-p231 1.8.6 1.8.6-p36 1.8.6-p110 1.8.6-p111 1.8.6-p114 1.8.6-p230 1.8.6-p286 1.8.6-p287 1.8.6-p368 1.8.6-p369 1.8.6-p383 1.8.6-p388 1.8.6-p398 1.8.6-p399 1.8.6-p420 1.8.7-preview1 1.8.7-preview2 1.8.7-preview3 1.8.7-preview4 1.8.7 1.8.7-p17 1.8.7-p22 1.8.7-p71 1.8.7-p72 1.8.7-p160 1.8.7-p173 1.8.7-p174 1.8.7-p248 1.8.7-p249 1.8.7-p299 1.8.7-p301 1.8.7-p302 1.8.7-p330 1.8.7-p334 1.8.7-p352 1.8.7-p357 1.8.7-p358 1.8.7-p370 1.8.7-p371 1.8.7-p373 1.8.7-p374 1.9.0-0 1.9.0-1 1.9.0-2 1.9.0-3 1.9.0-4 1.9.0-5 1.9.1-preview1 1.9.1-preview2 1.9.1-rc1 1.9.1-rc2 1.9.1-p0 1.9.1-p129 1.9.1-p243 1.9.1-p376 1.9.1-p378 1.9.1-p429 1.9.1-p430 1.9.1-p431 1.9.2-preview1 1.9.2-preview3 1.9.2-rc1 1.9.2-rc2 1.9.2-p0 1.9.2-p136 1.9.2-p180 1.9.2-p290 1.9.2-p318 1.9.2-p320 1.9.2-p330 1.9.3-dev 1.9.3-preview1 1.9.3-rc1 1.9.3-p0 1.9.3-p105 1.9.3-p125 1.9.3-p194 1.9.3-p286 1.9.3-p327 1.9.3-p362 1.9.3-p374 1.9.3-p385 1.9.3-p392 1.9.3-p426 1.9.3-p429 1.9.3-p448 1.9.3-p484 1.9.3-p545 1.9.3-p547 1.9.3-p550 1.9.3-p551 2.0.0-dev 2.0.0-preview1 2.0.0-preview2 2.0.0-rc1 2.0.0-rc2 2.0.0-p0 2.0.0-p195 2.0.0-p247 2.0.0-p353 2.0.0-p451 2.0.0-p481 2.0.0-p576 2.0.0-p594 2.0.0-p598 2.0.0-p643 2.0.0-p645 2.0.0-p647 2.0.0-p648 2.1.0-preview1 2.1.0-preview2 2.1.0-rc1 2.1.0 2.1-dev 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.1.10 2.2.0-preview1 2.2.0-preview2 2.2.0-rc1 2.2.0 2.2-dev 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.2.6 2.2.7 2.2.8 2.2.9 2.2.10 2.3.0-preview1 2.3.0-preview2 2.3.0 2.3-dev 2.3.1 2.3.2 2.3.3 2.3.4 2.3.5 2.3.6 2.3.7 2.3.8 2.4.0-preview1 2.4.0-preview2 2.4.0-preview3 2.4.0-rc1 2.4.0 2.4-dev 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.4.7 2.4.8 2.4.9 2.4.10 2.5.0-preview1 2.5.0-rc1 2.5.0 2.5-dev 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 2.5.7 2.5.8 2.5.9 2.6.0-preview1 2.6.0-preview2 2.6.0-preview3 2.6.0-rc1 2.6.0-rc2 2.6.0 2.6-dev 2.6.1 2.6.2 2.6.3 2.6.4 2.6.5 2.6.6 2.6.7 2.6.8 2.6.9 2.6.10 2.7.0-preview1 2.7.0-preview2 2.7.0-preview3 2.7.0-rc1 2.7.0-rc2 2.7.0 2.7-dev 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 3.0.0-preview1 3.0.0-preview2 3.0.0-rc1 3.0.0 3.0-dev 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.1.0-preview1 3.1.0 3.1-dev 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.2.0-preview1 3.2.0-preview2 3.2.0-preview3 3.2.0-rc1 3.2.0 3.2-dev 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.2.6 3.2.7 3.3.0-preview1 3.3.0-preview2 3.3.0-preview3 3.3.0-rc1 3.3.0 3.3-dev 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.4.0-preview1 3.4.0-preview2 3.4.0-rc1 3.4.0 3.4-dev 3.4.1 3.4.2 3.5-dev artichoke-dev jruby-dev jruby-1.7.2 jruby-1.7.5 jruby-1.7.6 jruby-1.7.7 jruby-1.7.8 jruby-1.7.9 jruby-1.7.10 jruby-1.7.11 jruby-1.7.12 jruby-1.7.13 jruby-1.7.14 jruby-1.7.15 jruby-1.7.16 jruby-1.7.16.1 jruby-1.7.16.2 jruby-1.7.17 jruby-1.7.18 jruby-1.7.19 jruby-1.7.20 jruby-1.7.20.1 jruby-1.7.21 jruby-1.7.22 jruby-1.7.23 jruby-1.7.24 jruby-1.7.25 jruby-1.7.26 jruby-1.7.27 jruby-9.0.0.0.pre1 jruby-9.0.0.0.pre2 jruby-9.0.0.0.rc1 jruby-9.0.0.0.rc2 jruby-9.0.0.0 jruby-9.0.1.0 jruby-9.0.3.0 jruby-9.0.4.0 jruby-9.0.5.0 jruby-9.1.0.0-dev jruby-9.1.0.0 jruby-9.1.1.0 jruby-9.1.2.0 jruby-9.1.3.0 jruby-9.1.4.0 jruby-9.1.5.0 jruby-9.1.6.0 jruby-9.1.7.0 jruby-9.1.8.0 jruby-9.1.9.0 jruby-9.1.10.0 jruby-9.1.11.0 jruby-9.1.12.0 jruby-9.1.13.0 jruby-9.1.14.0 jruby-9.1.15.0 jruby-9.1.16.0 jruby-9.1.17.0 jruby-9.2.0.0-dev jruby-9.2.0.0 jruby-9.2.1.0-dev jruby-9.2.1.0 jruby-9.2.3.0 jruby-9.2.4.0 jruby-9.2.4.1 jruby-9.2.5.0 jruby-9.2.6.0 jruby-9.2.7.0 jruby-9.2.8.0 jruby-9.2.9.0 jruby-9.2.10.0 jruby-9.2.11.0 jruby-9.2.11.1 jruby-9.2.12.0 jruby-9.2.13.0 jruby-9.2.14.0 jruby-9.2.15.0 jruby-9.2.16.0 jruby-9.2.17.0 jruby-9.2.18.0 jruby-9.2.19.0 jruby-9.2.20.0 jruby-9.2.20.1 jruby-9.2.21.0 jruby-9.3.0.0 jruby-9.3.1.0 jruby-9.3.2.0 jruby-9.3.3.0 jruby-9.3.4.0 jruby-9.3.6.0 jruby-9.3.7.0 jruby-9.3.8.0 jruby-9.3.9.0 jruby-9.3.10.0 jruby-9.3.11.0 jruby-9.3.13.0 jruby-9.3.14.0 jruby-9.3.15.0 jruby-9.4.0.0 jruby-9.4.1.0 jruby-9.4.2.0 jruby-9.4.3.0 jruby-9.4.4.0 jruby-9.4.5.0 jruby-9.4.6.0 jruby-9.4.7.0 jruby-9.4.8.0 jruby-9.4.9.0 jruby-9.4.10.0 jruby-9.4.11.0 jruby-9.4.12.0 mruby-dev mruby-1.0.0 mruby-1.1.0 mruby-1.2.0 mruby-1.3.0 mruby-1.4.0 mruby-1.4.1 mruby-2.0.0 mruby-2.0.1 mruby-2.1.0 mruby-2.1.1 mruby-2.1.2 mruby-3.0.0 mruby-3.1.0 mruby-3.2.0 mruby-3.3.0 picoruby-3.0.0 rbx-2.2.2 rbx-2.2.3 rbx-2.2.4 rbx-2.2.5 rbx-2.2.6 rbx-2.2.7 rbx-2.2.8 rbx-2.2.9 rbx-2.2.10 rbx-2.3.0 rbx-2.4.0 rbx-2.4.1 rbx-2.5.0 rbx-2.5.1 rbx-2.5.2 rbx-2.5.3 rbx-2.5.4 rbx-2.5.5 rbx-2.5.6 rbx-2.5.7 rbx-2.5.8 rbx-2.6 rbx-2.7 rbx-2.8 rbx-2.9 rbx-2.10 rbx-2.11 rbx-2.71828182 rbx-3.0 rbx-3.1 rbx-3.2 rbx-3.3 rbx-3.4 rbx-3.5 rbx-3.6 rbx-3.7 rbx-3.8 rbx-3.9 rbx-3.10 rbx-3.11 rbx-3.12 rbx-3.13 rbx-3.14 rbx-3.15 rbx-3.16 rbx-3.17 rbx-3.18 rbx-3.19 rbx-3.20 rbx-3.21 rbx-3.22 rbx-3.23 rbx-3.24 rbx-3.25 rbx-3.26 rbx-3.27 rbx-3.28 rbx-3.29 rbx-3.30 rbx-3.31 rbx-3.32 rbx-3.33 rbx-3.34 rbx-3.35 rbx-3.36 rbx-3.37 rbx-3.38 rbx-3.39 rbx-3.40 rbx-3.41 rbx-3.42 rbx-3.43 rbx-3.44 rbx-3.45 rbx-3.46 rbx-3.47 rbx-3.48 rbx-3.49 rbx-3.50 rbx-3.51 rbx-3.52 rbx-3.53 rbx-3.54 rbx-3.55 rbx-3.56 rbx-3.57 rbx-3.58 rbx-3.59 rbx-3.60 rbx-3.61 rbx-3.62 rbx-3.63 rbx-3.64 rbx-3.65 rbx-3.66 rbx-3.67 rbx-3.68 rbx-3.69 rbx-3.70 rbx-3.71 rbx-3.72 rbx-3.73 rbx-3.74 rbx-3.75 rbx-3.76 rbx-3.77 rbx-3.78 rbx-3.79 rbx-3.80 rbx-3.81 rbx-3.82 rbx-3.83 rbx-3.84 rbx-3.85 rbx-3.86 rbx-3.87 rbx-3.88 rbx-3.89 rbx-3.90 rbx-3.91 rbx-3.92 rbx-3.93 rbx-3.94 rbx-3.95 rbx-3.96 rbx-3.97 rbx-3.98 rbx-3.99 rbx-3.100 rbx-3.101 rbx-3.102 rbx-3.103 rbx-3.104 rbx-3.105 rbx-3.106 rbx-3.107 rbx-4.0 rbx-4.1 rbx-4.2 rbx-4.3 rbx-4.4 rbx-4.5 rbx-4.6 rbx-4.7 rbx-4.8 rbx-4.9 rbx-4.10 rbx-4.11 rbx-4.12 rbx-4.13 rbx-4.14 rbx-4.15 rbx-4.16 rbx-4.18 rbx-4.19 rbx-4.20 rbx-5.0 ree-1.8.7-2011.03 ree-1.8.7-2011.12 ree-1.8.7-2012.01 ree-1.8.7-2012.02 ruby-dev truffleruby-dev truffleruby-1.0.0-rc10 truffleruby-1.0.0-rc11 truffleruby-1.0.0-rc12 truffleruby-1.0.0-rc13 truffleruby-1.0.0-rc14 truffleruby-1.0.0-rc15 truffleruby-1.0.0-rc16 truffleruby-1.0.0-rc2 truffleruby-1.0.0-rc3 truffleruby-1.0.0-rc5 truffleruby-1.0.0-rc6 truffleruby-1.0.0-rc7 truffleruby-1.0.0-rc8 truffleruby-1.0.0-rc9 truffleruby-19.0.0 truffleruby-19.1.0 truffleruby-19.2.0 truffleruby-19.2.0.1 truffleruby-19.3.0 truffleruby-19.3.0.2 truffleruby-19.3.1 truffleruby-20.0.0 truffleruby-20.1.0 truffleruby-20.2.0 truffleruby-20.3.0 truffleruby-21.0.0 truffleruby-21.1.0 truffleruby-21.2.0 truffleruby-21.2.0.1 truffleruby-21.3.0 truffleruby-22.0.0.2 truffleruby-22.1.0 truffleruby-22.2.0 truffleruby-22.3.0 truffleruby-22.3.1 truffleruby-23.0.0-preview1 truffleruby-23.0.0 truffleruby-23.1.0 truffleruby-23.1.1 truffleruby-23.1.2 truffleruby-24.0.0 truffleruby-24.0.1 truffleruby-24.0.2 truffleruby-24.1.0 truffleruby-24.1.1 truffleruby-24.1.2 truffleruby+graalvm-dev truffleruby+graalvm-20.1.0 truffleruby+graalvm-20.2.0 truffleruby+graalvm-20.3.0 truffleruby+graalvm-21.0.0 truffleruby+graalvm-21.1.0 truffleruby+graalvm-21.2.0 truffleruby+graalvm-21.3.0 truffleruby+graalvm-22.0.0.2 truffleruby+graalvm-22.1.0 truffleruby+graalvm-22.2.0 truffleruby+graalvm-22.3.0 truffleruby+graalvm-22.3.1 truffleruby+graalvm-23.0.0-preview1 truffleruby+graalvm-23.0.0 truffleruby+graalvm-23.1.0 truffleruby+graalvm-23.1.1 truffleruby+graalvm-23.1.2 truffleruby+graalvm-24.0.0 truffleruby+graalvm-24.0.1 truffleruby+graalvm-24.0.2 truffleruby+graalvm-24.1.0 truffleruby+graalvm-24.1.1 truffleruby+graalvm-24.1.2 ================================================ FILE: internal/versions/testdata/uninstall-asdfrc ================================================ pre_asdf_uninstall_uninstall-test = echo pre_asdf_uninstall_test $@ post_asdf_uninstall_uninstall-test = echo post_asdf_uninstall_test $@ ================================================ FILE: internal/versions/versions.go ================================================ // Package versions handles all operations pertaining to specific versions. // Install, uninstall, etc... package versions import ( "errors" "fmt" "io" "os" "regexp" "strings" "github.com/asdf-vm/asdf/internal/config" "github.com/asdf-vm/asdf/internal/hook" "github.com/asdf-vm/asdf/internal/installs" "github.com/asdf-vm/asdf/internal/plugins" "github.com/asdf-vm/asdf/internal/resolve" "github.com/asdf-vm/asdf/internal/shims" "github.com/asdf-vm/asdf/internal/toolversions" ) const ( systemVersion = "system" latestVersion = "latest" latestFilterRegex = "(?i)(^Available versions:|-src|-dev|-latest|-stm|[-\\.]rc|-milestone|-alpha|-beta|[-\\.]pre|-next|(a|b|c)[0-9]+|snapshot|master|main)" numericStartFilterRegex = "^\\s*[0-9]" noLatestVersionErrMsg = "no latest version found" ) // UninstallableVersionError is an error returned if someone tries to install the // system version. type UninstallableVersionError struct { toolName string versionType string } func (e UninstallableVersionError) Error() string { return fmt.Sprintf("uninstallable version %s of %s", e.versionType, e.toolName) } // NoVersionSetError is returned whenever an operation that requires a version // is not able to resolve one. type NoVersionSetError struct { toolName string } func (e NoVersionSetError) Error() string { // Eventually switch this to a more friendly error message, BATS tests fail // with this improvement // return fmt.Sprintf("no version set for plugin %s", e.toolName) return "no version set" } // VersionAlreadyInstalledError is returned whenever a version is already // installed. type VersionAlreadyInstalledError struct { toolName string version toolversions.Version } func (e VersionAlreadyInstalledError) Error() string { return fmt.Sprintf("version %s of %s is already installed", e.version.Value, e.toolName) } // InstallAll installs all specified versions of every tool for the current // directory. Typically this will just be a single version, if not already // installed, but it may be multiple versions if multiple versions for the tool // are specified in the .tool-versions file. func InstallAll(conf config.Config, dir string, stdOut io.Writer, stdErr io.Writer) (failures []error) { plugins, err := plugins.List(conf, false, false) if err != nil { return []error{fmt.Errorf("unable to list plugins: %w", err)} } // Ideally we should install these in the order they are specified in the // closest .tool-versions file, but for now that is too complicated to // implement. for _, plugin := range plugins { err := Install(conf, plugin, dir, stdOut, stdErr) if err != nil { failures = append(failures, err) } } return failures } // Install installs all specified versions of a tool for the current directory. // Typically this will just be a single version, if not already installed, but // it may be multiple versions if multiple versions for the tool are specified // in the .tool-versions file. func Install(conf config.Config, plugin plugins.Plugin, dir string, stdOut io.Writer, stdErr io.Writer) error { err := plugin.Exists() if err != nil { return err } versions, found, err := resolve.Version(conf, plugin, dir) if err != nil { return err } if !found || len(versions.Versions) == 0 { return NoVersionSetError{toolName: plugin.Name} } for _, version := range versions.Versions { iErr := InstallOneVersion(conf, plugin, version, false, stdOut, stdErr) var vaiErr VersionAlreadyInstalledError if errors.As(iErr, &vaiErr) { err = errors.Join(err, iErr) } else if iErr != nil { return iErr } } return err } // InstallVersion installs a version of a specific tool, the version may be an // exact version, or it may be `latest` or `latest` a regex query in order to // select the latest version matching the provided pattern. func InstallVersion(conf config.Config, plugin plugins.Plugin, version toolversions.Version, stdOut io.Writer, stdErr io.Writer) error { err := plugin.Exists() if err != nil { return err } resolvedVersion := "" if version.Type == latestVersion { resolvedVersion, err = Latest(plugin, version.Value) if err != nil { return err } } return InstallOneVersion(conf, plugin, resolvedVersion, false, stdOut, stdErr) } // InstallOneVersion installs a specific version of a specific tool func InstallOneVersion(conf config.Config, plugin plugins.Plugin, versionStr string, keepDownload bool, stdOut io.Writer, stdErr io.Writer) error { err := plugin.Exists() if err != nil { return err } if versionStr == systemVersion { return UninstallableVersionError{toolName: plugin.Name, versionType: systemVersion} } version := toolversions.Parse(versionStr) if version.Type == "path" { return UninstallableVersionError{toolName: plugin.Name, versionType: "path"} } downloadDir := installs.DownloadPath(conf, plugin, version) installDir := installs.InstallPath(conf, plugin, version) if installs.IsInstalled(conf, plugin, version) { return VersionAlreadyInstalledError{version: version, toolName: plugin.Name} } concurrency, _ := conf.Concurrency() env := map[string]string{ "ASDF_INSTALL_TYPE": version.Type, "ASDF_INSTALL_VERSION": version.Value, "ASDF_INSTALL_PATH": installDir, "ASDF_DOWNLOAD_PATH": downloadDir, "ASDF_CONCURRENCY": concurrency, } err = os.MkdirAll(downloadDir, 0o777) if err != nil { return fmt.Errorf("unable to create download dir: %w", err) } err = hook.RunWithOutput(conf, fmt.Sprintf("pre_asdf_download_%s", plugin.Name), []string{version.Value}, stdOut, stdErr) if err != nil { return fmt.Errorf("failed to run pre-download hook: %w", err) } err = plugin.RunCallback("download", []string{}, env, stdOut, stdErr) if _, ok := err.(plugins.NoCallbackError); err != nil && !ok { return fmt.Errorf("failed to run download callback: %w", err) } err = hook.RunWithOutput(conf, fmt.Sprintf("pre_asdf_install_%s", plugin.Name), []string{version.Value}, stdOut, stdErr) if err != nil { return fmt.Errorf("failed to run pre-install hook: %w", err) } err = os.MkdirAll(installDir, 0o777) if err != nil { return fmt.Errorf("unable to create install dir: %w", err) } err = plugin.RunCallback("install", []string{}, env, stdOut, stdErr) if err != nil { if rmErr := os.RemoveAll(installDir); rmErr != nil { fmt.Fprintf(stdErr, "failed to clean up '%s' due to %s\n", installDir, rmErr) } return fmt.Errorf("failed to run install callback: %w", err) } // Reshim err = shims.GenerateAll(conf, stdOut, stdErr) if err != nil { return fmt.Errorf("unable to generate shims post-install: %w", err) } err = hook.RunWithOutput(conf, fmt.Sprintf("post_asdf_install_%s", plugin.Name), []string{version.Value}, stdOut, stdErr) if err != nil { return fmt.Errorf("failed to run post-install hook: %w", err) } // delete download dir keep, err := conf.AlwaysKeepDownload() if err != nil { return err } if keep || keepDownload { return nil } err = os.RemoveAll(downloadDir) if err != nil { return fmt.Errorf("failed to remove download dir: %w", err) } return nil } // Latest invokes the plugin's latest-stable callback if it exists and returns // the version it returns. If the callback is missing it invokes the list-all // callback and returns the last version matching the query, if a query is // provided. func Latest(plugin plugins.Plugin, query string) (version string, err error) { var stdOut strings.Builder var stdErr strings.Builder err = plugin.RunCallback("latest-stable", []string{query}, map[string]string{}, &stdOut, &stdErr) if err == nil { versions := parseVersions(stdOut.String()) if len(versions) < 1 { return version, errors.New(noLatestVersionErrMsg) } return versions[len(versions)-1], nil } // Fallback to list-all if latest-stable fails if _, ok := err.(plugins.NoCallbackError); !ok { return version, err } allVersions, err := AllVersions(plugin) if err != nil { return version, err } versions := filterByRegex(allVersions, latestFilterRegex, false) // If no query specified by user default to selecting version with numeric start if query == "" { versions = filterByRegex(versions, numericStartFilterRegex, true) } else { versions = filterByExactMatch(versions, query) } if len(versions) < 1 { return version, errors.New(noLatestVersionErrMsg) } return versions[len(versions)-1], nil } // AllVersions returns a slice of all available versions for the tool managed by // the given plugin by invoking the plugin's list-all callback func AllVersions(plugin plugins.Plugin) (versions []string, err error) { var stdout strings.Builder var stderr strings.Builder err = plugin.RunCallback("list-all", []string{}, map[string]string{}, &stdout, &stderr) if err != nil { return versions, err } versions = parseVersions(stdout.String()) return versions, err } // Uninstall uninstalls a specific tool version. It invokes pre and // post-uninstall hooks if set, and runs the plugin's uninstall callback if // defined. func Uninstall(conf config.Config, plugin plugins.Plugin, rawVersion string, stdout, stderr io.Writer) error { version := toolversions.ParseFromCliArg(rawVersion) if version.Type == "latest" { return errors.New("'latest' is a special version value that cannot be used for uninstall command") } if !installs.IsInstalled(conf, plugin, version) { return errors.New("No such version") } err := hook.RunWithOutput(conf, fmt.Sprintf("pre_asdf_uninstall_%s", plugin.Name), []string{version.Value}, stdout, stderr) if err != nil { return err } // invoke uninstall callback if available installDir := installs.InstallPath(conf, plugin, version) env := map[string]string{ "ASDF_INSTALL_TYPE": version.Type, "ASDF_INSTALL_VERSION": version.Value, "ASDF_INSTALL_PATH": installDir, } err = plugin.RunCallback("uninstall", []string{}, env, stdout, stderr) if _, ok := err.(plugins.NoCallbackError); !ok && err != nil { return err } err = os.RemoveAll(installDir) if err != nil { return err } err = hook.RunWithOutput(conf, fmt.Sprintf("post_asdf_uninstall_%s", plugin.Name), []string{version.Value}, stdout, stderr) if err != nil { return err } return nil } func filterByExactMatch(allVersions []string, pattern string) (versions []string) { for _, version := range allVersions { if strings.HasPrefix(version, pattern) { versions = append(versions, version) } } return versions } func filterByRegex(allVersions []string, pattern string, keepMatch bool) (versions []string) { regex, _ := regexp.Compile(pattern) for _, version := range allVersions { match := regex.MatchString(version) if match == keepMatch { versions = append(versions, version) } } return versions } // future refactoring opportunity: this function is an exact copy of // resolve.parseVersion func parseVersions(rawVersions string) []string { var versions []string for _, version := range strings.Split(rawVersions, " ") { version = strings.TrimSpace(version) if len(version) > 0 { versions = append(versions, version) } } return versions } ================================================ FILE: internal/versions/versions_test.go ================================================ package versions import ( "fmt" "os" "path/filepath" "strings" "testing" "github.com/asdf-vm/asdf/internal/config" "github.com/asdf-vm/asdf/internal/plugins" "github.com/asdf-vm/asdf/internal/repotest" "github.com/asdf-vm/asdf/internal/toolversions" "github.com/stretchr/testify/assert" ) const testPluginName = "testlua" func TestInstallAll(t *testing.T) { t.Run("installs multiple tools when multiple tool versions are specified", func(t *testing.T) { conf, plugin := generateConfig(t) stdout, stderr := buildOutputs() currentDir := t.TempDir() secondPlugin := installPlugin(t, conf, "dummy_plugin", "another") version := "1.0.0" // write a version file content := fmt.Sprintf("%s %s\n%s %s", plugin.Name, version, secondPlugin.Name, version) writeVersionFile(t, currentDir, content) err := InstallAll(conf, currentDir, &stdout, &stderr) assert.Nil(t, err) assertVersionInstalled(t, conf.DataDir, plugin.Name, version) assertVersionInstalled(t, conf.DataDir, secondPlugin.Name, version) }) t.Run("only installs tools with versions specified for current directory", func(t *testing.T) { conf, plugin := generateConfig(t) stdout, stderr := buildOutputs() currentDir := t.TempDir() secondPlugin := installPlugin(t, conf, "dummy_plugin", "another") version := "1.0.0" // write a version file content := fmt.Sprintf("%s %s\n", plugin.Name, version) writeVersionFile(t, currentDir, content) err := InstallAll(conf, currentDir, &stdout, &stderr) assert.ErrorContains(t, err[0], "no version set") assertVersionInstalled(t, conf.DataDir, plugin.Name, version) assertNotInstalled(t, conf.DataDir, secondPlugin.Name, version) }) t.Run("installs all tools even after one fails to install", func(t *testing.T) { conf, plugin := generateConfig(t) stdout, stderr := buildOutputs() currentDir := t.TempDir() secondPlugin := installPlugin(t, conf, "dummy_plugin", "another") version := "1.0.0" // write a version file content := fmt.Sprintf("%s %s\n%s %s", secondPlugin.Name, "non-existent-version", plugin.Name, version) writeVersionFile(t, currentDir, content) err := InstallAll(conf, currentDir, &stdout, &stderr) assert.Empty(t, err) assertNotInstalled(t, conf.DataDir, secondPlugin.Name, version) assertVersionInstalled(t, conf.DataDir, plugin.Name, version) }) } func TestInstall(t *testing.T) { conf, plugin := generateConfig(t) stdout, stderr := buildOutputs() currentDir := t.TempDir() t.Run("installs version of tool specified for current directory", func(t *testing.T) { version := "1.0.0" // write a version file data := []byte(fmt.Sprintf("%s %s", plugin.Name, version)) err := os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666) assert.Nil(t, err) err = Install(conf, plugin, currentDir, &stdout, &stderr) assert.Nil(t, err) assertVersionInstalled(t, conf.DataDir, plugin.Name, version) }) t.Run("returns error when plugin doesn't exist", func(t *testing.T) { conf, _ := generateConfig(t) stdout, stderr := buildOutputs() err := Install(conf, plugins.New(conf, "non-existent"), currentDir, &stdout, &stderr) assert.IsType(t, plugins.PluginMissing{}, err) }) t.Run("returns error when no version set", func(t *testing.T) { conf, _ := generateConfig(t) stdout, stderr := buildOutputs() currentDir := t.TempDir() err := Install(conf, plugin, currentDir, &stdout, &stderr) assert.EqualError(t, err, "no version set") }) t.Run("if multiple versions are defined installs all of them", func(t *testing.T) { conf, plugin := generateConfig(t) stdout, stderr := buildOutputs() currentDir := t.TempDir() versions := "1.0.0 2.0.0" // write a version file data := []byte(fmt.Sprintf("%s %s", plugin.Name, versions)) err := os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666) assert.Nil(t, err) err = Install(conf, plugin, currentDir, &stdout, &stderr) assert.Nil(t, err) assertVersionInstalled(t, conf.DataDir, plugin.Name, "1.0.0") assertVersionInstalled(t, conf.DataDir, plugin.Name, "2.0.0") }) t.Run("if multiple versions are defined and installed returns an error", func(t *testing.T) { conf, plugin := generateConfig(t) stdout, stderr := buildOutputs() currentDir := t.TempDir() versions := "1.0.0 2.0.0" // write a version file data := []byte(fmt.Sprintf("%s %s", plugin.Name, versions)) err := os.WriteFile(filepath.Join(currentDir, ".tool-versions"), data, 0o666) assert.NoError(t, err) err = Install(conf, plugin, currentDir, &stdout, &stderr) assert.NoError(t, err) assertVersionInstalled(t, conf.DataDir, plugin.Name, "1.0.0") assertVersionInstalled(t, conf.DataDir, plugin.Name, "2.0.0") err = Install(conf, plugin, currentDir, &stdout, &stderr) assert.Error(t, err) // Expect a VersionAlreadyInstalledError var eerr VersionAlreadyInstalledError assert.ErrorAs(t, err, &eerr) }) } func TestInstallVersion(t *testing.T) { t.Setenv("ASDF_CONFIG_FILE", "testdata/asdfrc") t.Run("returns error when plugin doesn't exist", func(t *testing.T) { conf, _ := generateConfig(t) stdout, stderr := buildOutputs() version := toolversions.Version{Type: "version", Value: "1.2.3"} err := InstallVersion(conf, plugins.New(conf, "non-existent"), version, &stdout, &stderr) assert.IsType(t, plugins.PluginMissing{}, err) }) t.Run("installs latest version of tool when version is 'latest'", func(t *testing.T) { conf, plugin := generateConfig(t) stdout, stderr := buildOutputs() version := toolversions.Version{Type: "latest", Value: ""} err := InstallVersion(conf, plugin, version, &stdout, &stderr) assert.Nil(t, err) assertVersionInstalled(t, conf.DataDir, plugin.Name, "2.0.0") }) t.Run("installs specific version of tool", func(t *testing.T) { conf, plugin := generateConfig(t) stdout, stderr := buildOutputs() version := toolversions.Version{Type: "latest", Value: "^1."} err := InstallVersion(conf, plugin, version, &stdout, &stderr) assert.Nil(t, err) assertVersionInstalled(t, conf.DataDir, plugin.Name, "1.1.0") }) } func TestInstallOneVersion(t *testing.T) { t.Setenv("ASDF_CONFIG_FILE", "testdata/asdfrc") t.Run("returns error when plugin doesn't exist", func(t *testing.T) { conf, _ := generateConfig(t) stdout, stderr := buildOutputs() err := InstallOneVersion(conf, plugins.New(conf, "non-existent"), "1.2.3", false, &stdout, &stderr) assert.IsType(t, plugins.PluginMissing{}, err) }) t.Run("returns error when passed a path version", func(t *testing.T) { conf, plugin := generateConfig(t) stdout, stderr := buildOutputs() err := InstallOneVersion(conf, plugin, "path:/foo/bar", false, &stdout, &stderr) assert.ErrorContains(t, err, "uninstallable version path of testlua") }) t.Run("returns error when plugin version is 'system'", func(t *testing.T) { conf, plugin := generateConfig(t) stdout, stderr := buildOutputs() err := InstallOneVersion(conf, plugin, "system", false, &stdout, &stderr) assert.IsType(t, UninstallableVersionError{}, err) }) t.Run("returns error when version doesn't exist", func(t *testing.T) { version := "other-dummy" conf, plugin := generateConfig(t) stdout, stderr := buildOutputs() err := InstallOneVersion(conf, plugin, version, false, &stdout, &stderr) assert.Errorf(t, err, "failed to run install callback: exit status 1") want := "pre_asdf_download_lua other-dummy\npre_asdf_install_lua other-dummy\nDummy couldn't install version: other-dummy (on purpose)\n" assert.Equal(t, want, stdout.String()) assertNotInstalled(t, conf.DataDir, plugin.Name, version) }) t.Run("returns error when version already installed", func(t *testing.T) { conf, plugin := generateConfig(t) stdout, stderr := buildOutputs() err := InstallOneVersion(conf, plugin, "1.0.0", false, &stdout, &stderr) assert.Nil(t, err) assertVersionInstalled(t, conf.DataDir, plugin.Name, "1.0.0") // Install a second time err = InstallOneVersion(conf, plugin, "1.0.0", false, &stdout, &stderr) assert.Error(t, err) // Expect a VersionAlreadyInstalledError var eerr VersionAlreadyInstalledError assert.ErrorAs(t, err, &eerr) }) t.Run("creates download directory", func(t *testing.T) { conf, plugin := generateConfig(t) stdout, stderr := buildOutputs() err := InstallOneVersion(conf, plugin, "1.0.0", false, &stdout, &stderr) assert.Nil(t, err) downloadPath := filepath.Join(conf.DataDir, "downloads", plugin.Name, "1.0.0") pathInfo, err := os.Stat(downloadPath) assert.Nil(t, err) assert.True(t, pathInfo.IsDir()) }) t.Run("creates install directory", func(t *testing.T) { conf, plugin := generateConfig(t) stdout, stderr := buildOutputs() err := InstallOneVersion(conf, plugin, "1.0.0", false, &stdout, &stderr) assert.Nil(t, err) installPath := filepath.Join(conf.DataDir, "installs", plugin.Name, "1.0.0") pathInfo, err := os.Stat(installPath) assert.Nil(t, err) assert.True(t, pathInfo.IsDir()) }) t.Run("deletes install directory when install fails", func(t *testing.T) { conf, plugin := generateConfig(t) stdout, stderr := buildOutputs() installScript := filepath.Join(conf.DataDir, "plugins", plugin.Name, "bin", "install") f, err := os.OpenFile(installScript, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o777) assert.Nil(t, err) _, err = f.WriteString("\nexit 1") assert.Nil(t, err) err = f.Close() assert.Nil(t, err) err = InstallOneVersion(conf, plugin, "1.0.0", false, &stdout, &stderr) assert.Errorf(t, err, "failed to run install callback: exit status 1") installPath := filepath.Join(conf.DataDir, "installs", plugin.Name, "1.0.0") _, err = os.Stat(installPath) assert.True(t, os.IsNotExist(err)) }) t.Run("runs pre-download, pre-install and post-install hooks when installation successful", func(t *testing.T) { conf, plugin := generateConfig(t) stdout, stderr := buildOutputs() err := InstallOneVersion(conf, plugin, "1.0.0", false, &stdout, &stderr) assert.Nil(t, err) assert.Equal(t, "", stderr.String()) want := "pre_asdf_download_lua 1.0.0\npre_asdf_install_lua 1.0.0\npost_asdf_install_lua 1.0.0\n" assert.Equal(t, want, stdout.String()) }) t.Run("installs successfully when plugin exists but version does not", func(t *testing.T) { conf, plugin := generateConfig(t) stdout, stderr := buildOutputs() err := InstallOneVersion(conf, plugin, "1.0.0", false, &stdout, &stderr) assert.Nil(t, err) // Check download directory downloadPath := filepath.Join(conf.DataDir, "downloads", plugin.Name, "1.0.0") entries, err := os.ReadDir(downloadPath) assert.Nil(t, err) // mock plugin doesn't write anything assert.Empty(t, entries) // Check install directory assertVersionInstalled(t, conf.DataDir, plugin.Name, "1.0.0") }) t.Run("install successfully when plugin lacks download callback", func(t *testing.T) { conf, _ := generateConfig(t) stdout, stderr := buildOutputs() testPluginName := "no-download" _, err := repotest.InstallPlugin("dummy_plugin_no_download", conf.DataDir, testPluginName) assert.Nil(t, err) plugin := plugins.New(conf, testPluginName) err = InstallOneVersion(conf, plugin, "1.0.0", false, &stdout, &stderr) assert.Nil(t, err) // no-download install script prints 'install' assert.Equal(t, "install", stdout.String()) }) } func TestLatest(t *testing.T) { pluginName := "latest_test" conf, _ := generateConfig(t) _, err := repotest.InstallPlugin("dummy_legacy_plugin", conf.DataDir, pluginName) assert.Nil(t, err) plugin := plugins.New(conf, pluginName) t.Run("when plugin has a latest-stable callback invokes it and returns version it printed", func(t *testing.T) { pluginName := "latest-with-callback" _, err := repotest.InstallPlugin("dummy_plugin", conf.DataDir, pluginName) assert.Nil(t, err) plugin := plugins.New(conf, pluginName) version, err := Latest(plugin, "") assert.Nil(t, err) assert.Equal(t, "2.0.0", version) }) t.Run("when plugin has latest-stable callback invokes and it does not filter results", func(t *testing.T) { pluginName := "latest-with-dev-version" pluginDir, err := repotest.InstallPlugin("dummy_plugin", conf.DataDir, pluginName) assert.Nil(t, err) plugin := plugins.New(conf, pluginName) // Replace latest-stable script so it returns a dev version that would be otherwise filtered out latestScript := filepath.Join(pluginDir, "bin", "latest-stable") err = os.WriteFile(latestScript, []byte("#!/usr/bin/env bash\necho 1.2.3-dev"), 0o777) assert.Nil(t, err) version, err := Latest(plugin, "") assert.Nil(t, err) assert.Equal(t, "1.2.3-dev", version) }) t.Run("when given query matching no versions return empty slice of versions", func(t *testing.T) { version, err := Latest(plugin, "impossible-to-satisfy-query") assert.Error(t, err, "no latest version found") assert.Equal(t, version, "") }) t.Run("when given no query returns latest version of plugin", func(t *testing.T) { version, err := Latest(plugin, "") assert.Nil(t, err) assert.Equal(t, "5.1.0", version) }) t.Run("when given no query returns latest version of plugin", func(t *testing.T) { version, err := Latest(plugin, "4") assert.Nil(t, err) assert.Equal(t, "4.0.0", version) }) t.Run("when given no query returns latest version of plugin starting with number", func(t *testing.T) { pluginName := "no-latest-non-numeric-versions" pluginDir, err := repotest.InstallPlugin("dummy_plugin", conf.DataDir, pluginName) assert.Nil(t, err) plugin := plugins.New(conf, pluginName) // Replace list-all script so it returns some non-numeric versions listAllScript := filepath.Join(pluginDir, "bin", "list-all") err = os.WriteFile(listAllScript, []byte("#!/usr/bin/env bash\necho foobar 3.4.5 1.2.3-dev foobar2"), 0o777) assert.Nil(t, err) latestScript := filepath.Join(pluginDir, "bin", "latest-stable") err = os.Remove(latestScript) assert.Nil(t, err) version, err := Latest(plugin, "") assert.Nil(t, err) assert.Equal(t, "3.4.5", version) }) } func TestLatestWithSamples(t *testing.T) { tests := []struct { testFile string expectedOutput string }{ { testFile: "list-all-ruby", expectedOutput: "3.4.2", }, { testFile: "list-all-python", expectedOutput: "3.13.2t", }, { testFile: "list-all-elixir", expectedOutput: "1.18.2-otp-27", }, } for _, tt := range tests { pluginName := "latest_test" conf, _ := generateConfig(t) pluginDir, err := repotest.InstallPlugin("dummy_legacy_plugin", conf.DataDir, pluginName) assert.Nil(t, err) versionsFilePath, err := filepath.Abs(filepath.Join("testdata", tt.testFile)) assert.Nil(t, err) contents := "#!/usr/bin/env bash\ncat \"" + versionsFilePath + "\"" listAllPath := filepath.Join(pluginDir, "bin", "list-all") err = os.WriteFile(listAllPath, []byte(contents), 0o777) assert.Nil(t, err) plugin := plugins.New(conf, pluginName) version, err := Latest(plugin, "") assert.Nil(t, err) assert.Equal(t, tt.expectedOutput, version) } } func TestAllVersions(t *testing.T) { pluginName := "list-all-test" conf, _ := generateConfig(t) _, err := repotest.InstallPlugin("dummy_plugin", conf.DataDir, pluginName) assert.Nil(t, err) plugin := plugins.New(conf, pluginName) t.Run("returns slice of available versions from plugin", func(t *testing.T) { versions, err := AllVersions(plugin) assert.Nil(t, err) assert.Equal(t, versions, []string{"1.0.0", "1.1.0", "2.0.0"}) }) t.Run("returns error when callback missing", func(t *testing.T) { pluginName = "list-all-fail" _, err := repotest.InstallPlugin("dummy_plugin_no_download", conf.DataDir, pluginName) assert.Nil(t, err) plugin := plugins.New(conf, pluginName) versions, err := AllVersions(plugin) assert.Equal(t, err.(plugins.NoCallbackError).Error(), "Plugin named list-all-fail does not have a callback named list-all") assert.Empty(t, versions) }) } func TestUninstall(t *testing.T) { t.Setenv("ASDF_CONFIG_FILE", "testdata/uninstall-asdfrc") pluginName := "uninstall-test" conf, _ := generateConfig(t) _, err := repotest.InstallPlugin("dummy_plugin", conf.DataDir, pluginName) assert.Nil(t, err) plugin := plugins.New(conf, pluginName) stdout, stderr := buildOutputs() t.Run("returns error when version is 'latest'", func(t *testing.T) { stdout, stderr := buildOutputs() err := Uninstall(conf, plugin, "latest", &stdout, &stderr) assert.Error(t, err, "'latest' is a special version value that cannot be used for uninstall command") }) t.Run("returns an error when version not installed", func(t *testing.T) { err := Uninstall(conf, plugin, "4.0.0", &stdout, &stderr) assert.Error(t, err, "No such version") }) t.Run("uninstalls successfully when plugin and version are installed", func(t *testing.T) { err = InstallOneVersion(conf, plugin, "1.0.0", false, &stdout, &stderr) assert.Nil(t, err) err := Uninstall(conf, plugin, "1.0.0", &stdout, &stderr) assert.Nil(t, err) assertNotInstalled(t, conf.DataDir, plugin.Name, "1.0.0") }) t.Run("runs pre and post-uninstall hooks", func(t *testing.T) { stdout, stderr := buildOutputs() err = InstallOneVersion(conf, plugin, "1.0.0", false, &stdout, &stderr) assert.Nil(t, err) err := Uninstall(conf, plugin, "1.0.0", &stdout, &stderr) assert.Nil(t, err) want := "pre_asdf_uninstall_test 1.0.0\npost_asdf_uninstall_test 1.0.0\n" assert.Equal(t, want, stdout.String()) }) t.Run("invokes uninstall callback when present", func(t *testing.T) { stdout, stderr := buildOutputs() err = InstallOneVersion(conf, plugin, "1.0.0", false, &stdout, &stderr) assert.Nil(t, err) data := []byte("echo custom uninstall") err := os.WriteFile(filepath.Join(plugin.Dir, "bin", "uninstall"), data, 0o755) assert.Nil(t, err) err = Uninstall(conf, plugin, "1.0.0", &stdout, &stderr) assert.Nil(t, err) want := "pre_asdf_uninstall_test 1.0.0\ncustom uninstall\npost_asdf_uninstall_test 1.0.0\n" assert.Equal(t, want, stdout.String()) }) } // Helper functions func buildOutputs() (strings.Builder, strings.Builder) { var stdout strings.Builder var stderr strings.Builder return stdout, stderr } func assertVersionInstalled(t *testing.T, dataDir, pluginName, version string) { t.Helper() installDir := filepath.Join(dataDir, "installs", pluginName, version) installedVersionFile := filepath.Join(installDir, "version") bytes, err := os.ReadFile(installedVersionFile) assert.Nil(t, err, "expected file from install to exist") want := fmt.Sprintf("%s\n", version) assert.Equal(t, want, string(bytes), "got wrong version") entries, err := os.ReadDir(installDir) assert.Nil(t, err) var fileNames []string for _, e := range entries { fileNames = append(fileNames, e.Name()) } assert.Equal(t, fileNames, []string{"bin", "env", "version"}) } func assertNotInstalled(t *testing.T, dataDir, pluginName, version string) { t.Helper() installPath := filepath.Join(dataDir, "installs", pluginName, version) entries, err := os.ReadDir(installPath) if err != nil && !os.IsNotExist(err) { t.Errorf("failed to check directory %s due to error %s", installPath, err) } assert.Empty(t, entries) } func generateConfig(t *testing.T) (config.Config, plugins.Plugin) { t.Helper() testDataDir := t.TempDir() conf, err := config.LoadConfig() assert.Nil(t, err) conf.DataDir = testDataDir _, err = repotest.InstallPlugin("dummy_plugin", testDataDir, testPluginName) assert.Nil(t, err) return conf, plugins.New(conf, testPluginName) } func installPlugin(t *testing.T, conf config.Config, fixture, name string) plugins.Plugin { _, err := repotest.InstallPlugin(fixture, conf.DataDir, name) assert.Nil(t, err) return plugins.New(conf, name) } func writeVersionFile(t *testing.T, dir, contents string) { t.Helper() err := os.WriteFile(filepath.Join(dir, ".tool-versions"), []byte(contents), 0o666) assert.Nil(t, err) } ================================================ FILE: lib/commands/command-current.bash ================================================ # -*- sh -*- # shellcheck source=lib/functions/plugins.bash . "$(dirname "$(dirname "$0")")/lib/functions/plugins.bash" # shellcheck disable=SC2059 plugin_current_command() { local plugin_name=$1 local terminal_format=$2 check_if_plugin_exists "$plugin_name" local search_path search_path=$PWD local version_and_path version_and_path=$(find_versions "$plugin_name" "$search_path") local full_version full_version=$(cut -d '|' -f 1 <<<"$version_and_path") local version_file_path version_file_path=$(cut -d '|' -f 2 <<<"$version_and_path") local version_not_installed local description="" IFS=' ' read -r -a versions <<<"$full_version" for version in "${versions[@]}"; do if ! (check_if_version_exists "$plugin_name" "$version"); then version_not_installed="$version" fi done check_for_deprecated_plugin "$plugin_name" if [ -n "$version_not_installed" ]; then description="Not installed. Run \"asdf install $plugin $version\"" printf "$terminal_format" "$plugin" "$version" "$description" 1>&2 return 1 elif [ -z "$full_version" ]; then description="No version is set. Run \"asdf <global|shell|local> $plugin <version>\"" printf "$terminal_format" "$plugin" "______" "$description" 1>&2 return 126 else description="$version_file_path" printf "$terminal_format" "$plugin" "$full_version" "$description" fi } # shellcheck disable=SC2059 current_command() { local terminal_format="%-15s %-15s %-10s\n" local exit_status=0 local plugin # printf "$terminal_format" "PLUGIN" "VERSION" "SET BY CONFIG" # disable this until we release headings across the board if [ $# -eq 0 ]; then # shellcheck disable=SC2119 for plugin in $(plugin_list_command); do plugin_current_command "$plugin" "$terminal_format" done else plugin=$1 plugin_current_command "$plugin" "$terminal_format" exit_status="$?" fi exit "$exit_status" } # Warn if the plugin isn't using the updated legacy file api. check_for_deprecated_plugin() { local plugin_name=$1 local plugin_path plugin_path=$(get_plugin_path "$plugin_name") local legacy_config legacy_config=$(get_asdf_config_value "legacy_version_file") local deprecated_script="${plugin_path}/bin/get-version-from-legacy-file" local new_script="${plugin_path}/bin/list-legacy-filenames" if [ "$legacy_config" = "yes" ] && [ -f "$deprecated_script" ] && [ ! -f "$new_script" ]; then printf "Heads up! It looks like your %s plugin is out of date. You can update it with:\n\n" "$plugin_name" printf " asdf plugin-update %s\n\n" "$plugin_name" fi } current_command "$@" ================================================ FILE: lib/commands/command-env.bash ================================================ # -*- sh -*- shim_env_command() { local shim_name="$1" local env_cmd="${2}" local env_args=("${@:3}") if [ -z "$shim_name" ]; then printf "usage: asdf env <command>\n" exit 1 fi if [ -z "$env_cmd" ]; then env_cmd="env" fi shim_env() { "$env_cmd" "${env_args[@]}" } with_shim_executable "$shim_name" shim_env || exit $? } shim_env_command "$@" ================================================ FILE: lib/commands/command-exec.bash ================================================ # -*- sh -*- shim_exec_command() { local shim_name shim_name=$(basename "$1") local shim_args=("${@:2}") if [ -z "$shim_name" ]; then printf "usage: asdf exec <command>\n" exit 1 fi exec_shim() { local plugin_name="$1" local version="$2" local executable_path="$3" if [ ! -x "$executable_path" ]; then printf "No %s executable found for %s %s\n" "$shim_name" "$plugin_name" "$version" >&2 exit 2 fi asdf_run_hook "pre_${plugin_name}_${shim_name}" "${shim_args[@]}" pre_status=$? if [ "$pre_status" -ne 0 ]; then return "$pre_status" fi exec "$executable_path" "${shim_args[@]}" } with_shim_executable "$shim_name" exec_shim || exit $? } shim_exec_command "$@" ================================================ FILE: lib/commands/command-export-shell-version.bash ================================================ # -*- sh -*- # shellcheck source=lib/functions/versions.bash . "$(dirname "$(dirname "$0")")/lib/functions/versions.bash" # Output from this command must be executable shell code shell_command() { local asdf_shell="$1" shift if [ "$#" -lt "2" ]; then printf "Usage: asdf shell <name> {<version>|--unset}\n" >&2 printf "false\n" exit 1 fi local plugin=$1 local version=$2 local upcase_name upcase_name=$(tr '[:lower:]-' '[:upper:]_' <<<"$plugin") local version_env_var="ASDF_${upcase_name}_VERSION" if [ "$version" = "--unset" ]; then case "$asdf_shell" in fish) printf "set -e %s\n" "$version_env_var" ;; elvish) # Elvish doesn't have a `source` command, and eval is banned, so the # var name and value are printed on separate lines for asdf.elv to parse # 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" ;; esac exit 0 fi if [ "$version" = "latest" ]; then version=$(latest_command "$plugin") fi if ! (check_if_version_exists "$plugin" "$version"); then version_not_installed_text "$plugin" "$version" 1>&2 printf "false\n" exit 1 fi case "$asdf_shell" in fish) printf "set -gx %s \"%s\"\n" "$version_env_var" "$version" ;; elvish) # Elvish doesn't have a `source` command, and eval is banned, so the # var name and value are printed on separate lines for asdf.elv to parse # 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" ;; esac } shell_command "$@" ================================================ FILE: lib/commands/command-global.bash ================================================ # -*- sh -*- # shellcheck source=lib/commands/version_commands.bash . "$(dirname "$ASDF_CMD_FILE")/version_commands.bash" version_command global "$@" ================================================ FILE: lib/commands/command-help.bash ================================================ # -*- sh -*- # shellcheck source=lib/functions/versions.bash . "$(dirname "$(dirname "$0")")/lib/functions/versions.bash" asdf_help() { printf "version: %s\n\n" "$(asdf_version)" cat "$(asdf_dir)/help.txt" } asdf_moto() { cat <<EOF "Late but latest" -- Rajinikanth EOF } asdf_extension_cmds() { local plugins_path plugin_path ext_cmd_path ext_cmds plugin plugins_path="$(get_plugin_path)" for plugin_path in "$plugins_path"/*/; do plugin="$(basename "$plugin_path")" ext_cmd_path="$plugin_path/lib/commands" ext_cmds="$(find "$ext_cmd_path" -name "command*.bash" 2>/dev/null)" if [[ -n $ext_cmds ]]; then printf "\nPLUGIN %s\n" "$plugin" for ext_cmd in $ext_cmds; do ext_cmd_name="$(basename "$ext_cmd")" sed "s/-/ /g;s/.bash//;s/command-*/ asdf $plugin/;" <<<"$ext_cmd_name" done | sort fi done } help_command() { local plugin_name="$1" local tool_version="$2" local plugin_path # If plugin name is present as first argument output plugin help info if [ -n "$plugin_name" ]; then plugin_path=$(get_plugin_path "$plugin_name") if [ -d "$plugin_path" ]; then if [ -f "${plugin_path}/bin/help.overview" ]; then if [ -n "$tool_version" ]; then # TODO: Refactor this code out into helper functions in utils.bash IFS=':' read -r -a version_info <<<"$tool_version" if [ "${version_info[0]}" = "ref" ]; then local install_type="${version_info[0]}" local version="${version_info[1]}" else local install_type="version" if [ "${version_info[0]}" = "latest" ]; then local version version=$(latest_command "$plugin_name" "${version_info[1]}") else local version="${version_info[0]}" fi fi local install_path install_path=$(get_install_path "$plugin_name" "$install_type" "$version") ( # shellcheck disable=SC2031 export ASDF_INSTALL_TYPE=$install_type # shellcheck disable=SC2031 export ASDF_INSTALL_VERSION=$version # shellcheck disable=SC2031 export ASDF_INSTALL_PATH=$install_path print_plugin_help "$plugin_path" ) else (print_plugin_help "$plugin_path") fi else printf "No documentation for plugin %s\n" "$plugin_name" >&2 exit 1 fi else printf "No plugin named %s\n" "$plugin_name" >&2 exit 1 fi else # Otherwise output general asdf help asdf_help asdf_extension_cmds asdf_moto fi } print_plugin_help() { local plugin_path=$1 # Eventually @jthegedus or someone else will format the output from these # scripts in a certain way. "${plugin_path}"/bin/help.overview if [ -f "${plugin_path}"/bin/help.deps ]; then "${plugin_path}"/bin/help.deps fi if [ -f "${plugin_path}"/bin/help.config ]; then "${plugin_path}"/bin/help.config fi if [ -f "${plugin_path}"/bin/help.links ]; then "${plugin_path}"/bin/help.links fi } help_command "$@" ================================================ FILE: lib/commands/command-info.bash ================================================ # -*- sh -*- # shellcheck source=lib/functions/plugins.bash . "$(dirname "$(dirname "$0")")/lib/functions/plugins.bash" 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" "BASH VERSION" "$BASH_VERSION" printf "%s:\n%s\n\n" "ASDF VERSION" "$(asdf_version)" 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)" } info_command "$@" ================================================ FILE: lib/commands/command-install.bash ================================================ # -*- sh -*- # shellcheck source=lib/functions/versions.bash . "$(dirname "$(dirname "$0")")/lib/functions/versions.bash" # shellcheck source=lib/commands/reshim.bash . "$(dirname "$ASDF_CMD_FILE")/reshim.bash" # shellcheck source=lib/functions/installs.bash . "$(dirname "$(dirname "$0")")/lib/functions/installs.bash" install_command "$@" ================================================ FILE: lib/commands/command-latest.bash ================================================ # -*- sh -*- # shellcheck source=lib/functions/versions.bash . "$(dirname "$(dirname "$0")")/lib/functions/versions.bash" latest_command "$@" ================================================ FILE: lib/commands/command-list-all.bash ================================================ # -*- sh -*- # shellcheck source=lib/functions/versions.bash . "$(dirname "$(dirname "$0")")/lib/functions/versions.bash" list_all_command "$@" ================================================ FILE: lib/commands/command-list.bash ================================================ # -*- sh -*- list_command() { local plugin_name=$1 local query=$2 if [ -z "$plugin_name" ]; then local plugins_path plugins_path=$(get_plugin_path) if find "$plugins_path" -mindepth 1 -type d &>/dev/null; then for plugin_path in "$plugins_path"/*/; do plugin_name=$(basename "$plugin_path") printf "%s\n" "$plugin_name" display_installed_versions "$plugin_name" "$query" done else printf "%s\n" 'No plugins installed' fi else check_if_plugin_exists "$plugin_name" display_installed_versions "$plugin_name" "$query" fi } display_installed_versions() { local plugin_name=$1 local query=$2 local versions local current_version local flag versions=$(list_installed_versions "$plugin_name") if [[ $query ]]; then versions=$(printf "%s\n" "$versions" | grep -E "^\s*$query") if [ -z "${versions}" ]; then display_error "No compatible versions installed ($plugin_name $query)" exit 1 fi fi if [ -n "${versions}" ]; then current_version=$(cut -d '|' -f 1 <<<"$(find_versions "$plugin_name" "$PWD")") for version in $versions; do flag=" " if [[ "$version" == "$current_version" ]]; then flag=" *" fi printf "%s%s\n" "$flag" "$version" done else display_error ' No versions installed' fi } list_command "$@" ================================================ FILE: lib/commands/command-local.bash ================================================ # -*- sh -*- # shellcheck source=lib/commands/version_commands.bash . "$(dirname "$ASDF_CMD_FILE")/version_commands.bash" local_command "$@" ================================================ FILE: lib/commands/command-plugin-add.bash ================================================ # -*- sh -*- # shellcheck source=lib/functions/plugins.bash . "$(dirname "$(dirname "$0")")/lib/functions/plugins.bash" plugin_add_command "$@" ================================================ FILE: lib/commands/command-plugin-list-all.bash ================================================ # -*- sh -*- plugin_list_all_command() { initialize_or_update_plugin_repository local plugins_index_path plugins_index_path="$(asdf_data_dir)/repository/plugins" local plugins_local_path plugins_local_path="$(get_plugin_path)" if find "$plugins_index_path" -mindepth 1 -type d &>/dev/null; then ( for index_plugin in "$plugins_index_path"/*; do index_plugin_name=$(basename "$index_plugin") source_url=$(get_plugin_source_url "$index_plugin_name") installed_flag=" " [[ -d "${plugins_local_path}/${index_plugin_name}" ]] && installed_flag='*' printf "%s\t%s\n" "$index_plugin_name" "$installed_flag$source_url" done ) | awk '{ printf("%-28s", $1); sub(/^[^*]/, " &", $2); $1=""; print $0 }' else printf "%s%s\n" "error: index of plugins not found at " "$plugins_index_path" fi } plugin_list_all_command "$@" ================================================ FILE: lib/commands/command-plugin-list.bash ================================================ # -*- sh -*- # shellcheck source=lib/functions/plugins.bash . "$(dirname "$(dirname "$0")")/lib/functions/plugins.bash" plugin_list_command "$@" ================================================ FILE: lib/commands/command-plugin-push.bash ================================================ # -*- sh -*- plugin_push_command() { local plugin_name=$1 if [ "$plugin_name" = "--all" ]; then for dir in "$(asdf_data_dir)"/plugins/*/; do printf "Pushing %s...\n" "$(basename "$dir")" (cd "$dir" && git push) done else local plugin_path plugin_path=$(get_plugin_path "$plugin_name") check_if_plugin_exists "$plugin_name" printf "Pushing %s...\n" "$plugin_name" (cd "$plugin_path" && git push) fi } plugin_push_command "$@" ================================================ FILE: lib/commands/command-plugin-remove.bash ================================================ # -*- sh -*- plugin_remove_command() { local plugin_name=$1 check_if_plugin_exists "$plugin_name" local plugin_path plugin_path=$(get_plugin_path "$plugin_name") asdf_run_hook "pre_asdf_plugin_remove" "$plugin_name" asdf_run_hook "pre_asdf_plugin_remove_${plugin_name}" if [ -f "${plugin_path}/bin/pre-plugin-remove" ]; then ( export ASDF_PLUGIN_PATH=$plugin_path "${plugin_path}/bin/pre-plugin-remove" ) fi rm -rf "$plugin_path" rm -rf "$(asdf_data_dir)/installs/${plugin_name}" rm -rf "$(asdf_data_dir)/downloads/${plugin_name}" 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}" } plugin_remove_command "$@" ================================================ FILE: lib/commands/command-plugin-test.bash ================================================ # -*- sh -*- # shellcheck source=lib/functions/versions.bash . "$(dirname "$(dirname "$0")")/lib/functions/versions.bash" # shellcheck source=lib/functions/plugins.bash . "$(dirname "$(dirname "$0")")/lib/functions/plugins.bash" # shellcheck source=lib/commands/reshim.bash . "$(dirname "$ASDF_CMD_FILE")/reshim.bash" # shellcheck source=lib/functions/installs.bash . "$(dirname "$(dirname "$0")")/lib/functions/installs.bash" plugin_test_command() { local plugin_name="$1" local plugin_url="$2" shift 2 local exit_code local TEST_DIR fail_test() { printf "FAILED: %s\n" "$1" rm -rf "$TEST_DIR" exit 1 } if [ -z "$plugin_name" ] || [ -z "$plugin_url" ]; then fail_test "please provide a plugin name and url" fi local plugin_gitref local tool_version local interpret_args_literally local skip_next_arg for arg; do shift if [ -n "${skip_next_arg}" ]; then skip_next_arg= elif [ -n "${interpret_args_literally}" ]; then set -- "$@" "${arg}" else case "${arg}" in --asdf-plugin-gitref) plugin_gitref="$1" skip_next_arg=true ;; --asdf-tool-version) tool_version="$1" skip_next_arg=true ;; --) interpret_args_literally=true ;; *) set -- "$@" "${arg}" ;; esac fi done if [ -z "$plugin_gitref" ]; then plugin_remote_default_branch=$(git ls-remote --symref "$plugin_url" HEAD | awk '{ sub(/refs\/heads\//, ""); print $2; exit }') plugin_gitref=${3:-${plugin_remote_default_branch}} fi if [ "$#" -eq 1 ]; then set -- "${SHELL:-sh}" -c "$1" fi TEST_DIR=$(mktemp -dt asdf.XXXXXX) cp -R "$(asdf_dir)/bin" "$TEST_DIR" cp -R "$(asdf_dir)/lib" "$TEST_DIR" cp "$(asdf_dir)/asdf.sh" "$TEST_DIR" plugin_test() { export ASDF_DIR=$TEST_DIR export ASDF_DATA_DIR=$TEST_DIR # shellcheck disable=SC1090 . "$ASDF_DIR/asdf.sh" if ! (plugin_add_command "$plugin_name" "$plugin_url"); then fail_test "could not install $plugin_name from $plugin_url" fi # shellcheck disable=SC2119 if ! (plugin_list_command | grep -q "^$plugin_name$"); then fail_test "$plugin_name was not properly installed" fi if ! (plugin_update_command "$plugin_name" "$plugin_gitref"); then fail_test "failed to checkout $plugin_name gitref: $plugin_gitref" fi local plugin_path plugin_path=$(get_plugin_path "$plugin_name") local list_all="$plugin_path/bin/list-all" if grep -q api.github.com "$list_all"; then if ! grep -q Authorization "$list_all"; then printf "\nLooks like %s/bin/list-all relies on GitHub releases\n" "$plugin_name" printf "but it does not properly sets an Authorization header to prevent\n" printf "GitHub API rate limiting.\n\n" printf "See https://github.com/asdf-vm/asdf/blob/master/docs/creating-plugins.md#github-api-rate-limiting\n" fail_test "$plugin_name/bin/list-all does not set GitHub Authorization token" fi # test for most common token names we have on plugins. If both are empty show this warning if [ -z "$OAUTH_TOKEN" ] && [ -z "$GITHUB_API_TOKEN" ]; then printf "%s/bin/list-all is using GitHub API, just be sure you provide an API Authorization token\n" "$plugin_name" printf "via your CI env GITHUB_API_TOKEN. This is the current rate_limit:\n\n" curl -s https://api.github.com/rate_limit printf "\n" fi fi local versions # shellcheck disable=SC2046 if ! read -r -a versions <<<$(list_all_command "$plugin_name"); then fail_test "list-all exited with an error" fi if [ ${#versions} -eq 0 ]; then fail_test "list-all did not return any version" fi local version # Use the version passed in if it was set. Otherwise grab the latest # version from the versions list if [ -z "$tool_version" ] || [[ "$tool_version" == *"latest"* ]]; then version="$(latest_command "$plugin_name" "$(sed -e 's#latest##;s#^:##' <<<"$tool_version")")" if [ -z "$version" ]; then fail_test "could not get latest version" fi else version="$tool_version" fi if ! (install_command "$plugin_name" "$version"); then fail_test "install exited with an error" fi cd "$TEST_DIR" || fail_test "could not cd $TEST_DIR" if ! (local_command "$plugin_name" "$version"); then fail_test "install did not add the requested version" fi if ! (reshim_command "$plugin_name"); then fail_test "could not reshim plugin" fi if [ "$#" -gt 0 ]; then "$@" exit_code=$? if [ $exit_code != 0 ]; then fail_test "$* failed with exit code $exit_code" fi fi # Assert the scripts in bin are executable by asdf for filename in "$ASDF_DIR/plugins/$plugin_name/bin"/*; do if [ ! -x "$filename" ]; then fail_test "Incorrect permissions on $filename. Must be executable by asdf" fi done # Assert that a license file exists in the plugin repo and is not empty license_file="$ASDF_DIR/plugins/$plugin_name/LICENSE" if [ -f "$license_file" ]; then if [ ! -s "$license_file" ]; then fail_test "LICENSE file in the plugin repository must not be empty" fi else fail_test "LICENSE file must be present in the plugin repository" fi } # run test in a subshell (plugin_test "$@") exit_code=$? rm -rf "$TEST_DIR" exit $exit_code } plugin_test_command "$@" ================================================ FILE: lib/commands/command-plugin-update.bash ================================================ # -*- sh -*- # shellcheck source=lib/functions/plugins.bash . "$(dirname "$(dirname "$0")")/lib/functions/plugins.bash" plugin_update_command "$@" ================================================ FILE: lib/commands/command-reshim.bash ================================================ # -*- sh -*- # shellcheck source=lib/commands/reshim.bash . "$(dirname "$ASDF_CMD_FILE")/reshim.bash" reshim_command "$@" ================================================ FILE: lib/commands/command-shim-versions.bash ================================================ # -*- sh -*- shim_versions_command() { local shim_name=$1 shim_plugin_versions "$shim_name" } shim_versions_command "$@" ================================================ FILE: lib/commands/command-uninstall.bash ================================================ # -*- sh -*- # shellcheck source=lib/commands/reshim.bash . "$(dirname "$ASDF_CMD_FILE")/reshim.bash" uninstall_command() { local plugin_name=$1 local full_version=$2 local plugin_path plugin_path=$(get_plugin_path "$plugin_name") check_if_plugin_exists "$plugin_name" IFS=':' read -r -a version_info <<<"$full_version" if [ "${version_info[0]}" = "ref" ]; then local install_type="${version_info[0]}" local version="${version_info[1]}" else local install_type="version" local version="${version_info[0]}" fi local install_path install_path=$(get_install_path "$plugin_name" "$install_type" "$version") if [ ! -d "$install_path" ]; then display_error "No such version" exit 1 fi asdf_run_hook "pre_asdf_uninstall_${plugin_name}" "$full_version" remove_shims_for_version "$plugin_name" "$full_version" if [ -f "${plugin_path}/bin/uninstall" ]; then ( export ASDF_INSTALL_TYPE=$install_type export ASDF_INSTALL_VERSION=$version export ASDF_INSTALL_PATH=$install_path "${plugin_path}/bin/uninstall" ) else rm -rf "$install_path" fi asdf_run_hook "post_asdf_uninstall_${plugin_name}" "$full_version" } remove_shims_for_version() { local plugin_name=$1 local full_version=$2 for shim_path in $(plugin_shims "$plugin_name" "$full_version"); do remove_shim_for_version "$plugin_name" "$full_version" "$shim_path" done } uninstall_command "$@" ================================================ FILE: lib/commands/command-update.bash ================================================ # -*- sh -*- update_command() { printf "Upgrading asdf via asdf update is no longer supported. Please use your OS\npackage manager (Homebrew, APT, etc...) to upgrade asdf or download the\nlatest asdf binary manually from the asdf website.\n\nPlease visit https://asdf-vm.com/ or https://github.com/asdf-vm/asdf for more\ndetails.\n" exit 1 } update_command "$@" ================================================ FILE: lib/commands/command-version.bash ================================================ # -*- sh -*- asdf_version ================================================ FILE: lib/commands/command-where.bash ================================================ # -*- sh -*- where_command() { local plugin_name=$1 local full_version=$2 check_if_plugin_exists "$plugin_name" local version local install_type="version" if [[ -z ${full_version} ]]; then local version_and_path local versions version_and_path=$(find_versions "$plugin_name" "$PWD") versions=$(cut -d '|' -f 1 <<<"$version_and_path") IFS=' ' read -r -a plugin_versions <<<"$versions" version="${plugin_versions[0]}" else local -a version_info IFS=':' read -r -a version_info <<<"$full_version" if [ "${version_info[0]}" = "ref" ]; then install_type="${version_info[0]}" version="${version_info[1]}" else version="${version_info[0]}" fi fi if [ -z "$version" ]; then display_no_version_set "$plugin_name" exit 1 fi local install_path install_path=$(get_install_path "$plugin_name" "$install_type" "$version") if [ -d "$install_path" ]; then printf "%s\n" "$install_path" exit 0 else if [ "$version" = "system" ]; then printf "System version is selected\n" exit 1 else printf "Version not installed\n" exit 1 fi fi } where_command "$@" ================================================ FILE: lib/commands/command-which.bash ================================================ # -*- sh -*- which_command() { local shim_name shim_name=$(basename "$1") if [ -z "$shim_name" ]; then printf "usage: asdf which <command>\n" exit 1 fi print_exec() { local plugin_name="$1" local version="$2" local executable_path="$3" if [ ! -x "$executable_path" ]; then printf "No %s executable found for %s %s\n" "$shim_name" "$plugin_name" "$version" >&2 exit 1 fi printf "%s\n" "$executable_path" exit 0 } with_shim_executable "$shim_name" print_exec || exit 1 } which_command "$@" ================================================ FILE: lib/commands/reshim.bash ================================================ remove_shim_for_version() { local plugin_name=$1 local version=$2 local shim_name shim_name=$(basename "$3") local shim_path shim_path="$(asdf_data_dir)/shims/$shim_name" local count_installed count_installed=$(list_installed_versions "$plugin_name" | wc -l) if ! grep -x "# asdf-plugin: $plugin_name $version" "$shim_path" &>/dev/null; then return 0 fi sed -i.bak -e "/# asdf-plugin: $plugin_name $version"'$/d' "$shim_path" rm "$shim_path".bak if ! grep -q "# asdf-plugin:" "$shim_path" || [ "$count_installed" -eq 0 ]; then rm -f "$shim_path" fi } reshim_command() { local plugin_name=$1 local full_version=$2 if [ -z "$plugin_name" ]; then local plugins_path plugins_path=$(get_plugin_path) if find "$plugins_path" -mindepth 1 -type d &>/dev/null; then for plugin_path in "$plugins_path"/*/; do plugin_name=$(basename "$plugin_path") reshim_command "$plugin_name" done fi return 0 fi check_if_plugin_exists "$plugin_name" ensure_shims_dir if [ "$full_version" != "" ]; then # generate for the whole package version asdf_run_hook "pre_asdf_reshim_$plugin_name" "$full_version" generate_shims_for_version "$plugin_name" "$full_version" asdf_run_hook "post_asdf_reshim_$plugin_name" "$full_version" else # generate for all versions of the package local plugin_installs_path plugin_installs_path="$(asdf_data_dir)/installs/${plugin_name}" for install in "${plugin_installs_path}"/*/; do local full_version_name full_version_name=$(basename "$install" | sed 's/ref\-/ref\:/') asdf_run_hook "pre_asdf_reshim_$plugin_name" "$full_version_name" generate_shims_for_version "$plugin_name" "$full_version_name" remove_obsolete_shims "$plugin_name" "$full_version_name" asdf_run_hook "post_asdf_reshim_$plugin_name" "$full_version_name" done fi } ensure_shims_dir() { # Create shims dir if doesn't exist if [ ! -d "$(asdf_data_dir)/shims" ]; then mkdir "$(asdf_data_dir)/shims" fi } write_shim_script() { local plugin_name=$1 local version=$2 local executable_path=$3 if ! is_executable "$executable_path"; then return 0 fi local executable_name executable_name=$(basename "$executable_path") local shim_path shim_path="$(asdf_data_dir)/shims/$executable_name" local temp_dir temp_dir=${TMPDIR:-/tmp} local temp_versions_path temp_versions_path=$(mktemp "$temp_dir/asdf-command-reshim-write-shims.XXXXXX") cat <<EOF >"$temp_versions_path" # asdf-plugin: ${plugin_name} ${version} EOF if [ -f "$shim_path" ]; then grep '^#\sasdf-plugin:\s' <"$shim_path" >>"$temp_versions_path" fi cat <<EOF >"$shim_path" #!/usr/bin/env bash $(sort -u <"$temp_versions_path") exec $(asdf_dir)/bin/asdf exec "${executable_name}" "\$@" # asdf_allow: ' asdf ' EOF rm "$temp_versions_path" chmod +x "$shim_path" } generate_shims_for_version() { local plugin_name=$1 local full_version=$2 local all_executable_paths IFS=$'\n' read -rd '' -a all_executable_paths <<<"$(plugin_executables "$plugin_name" "$full_version")" for executable_path in "${all_executable_paths[@]}"; do write_shim_script "$plugin_name" "$full_version" "$executable_path" done } remove_obsolete_shims() { local plugin_name=$1 local full_version=$2 local shims shims=$(plugin_shims "$plugin_name" "$full_version" | xargs -IX basename X | sort) local exec_names exec_names=$(plugin_executables "$plugin_name" "$full_version" | xargs -IX basename X | sort) local obsolete_shims local formatted_shims local formatted_exec_names local temp_dir temp_dir=${TMPDIR:-/tmp} # comm only takes to files, so we write this data to temp files so we can # pass it to comm. formatted_shims="$(mktemp "$temp_dir/asdf-command-reshim-formatted-shims.XXXXXX")" printf "%s\n" "$shims" >"$formatted_shims" formatted_exec_names="$(mktemp "$temp_dir/asdf-command-reshim-formatted-exec-names.XXXXXX")" printf "%s\n" "$exec_names" >"$formatted_exec_names" obsolete_shims=$(comm -23 "$formatted_shims" "$formatted_exec_names") rm -f "$formatted_exec_names" "$formatted_shims" for shim_name in $obsolete_shims; do remove_shim_for_version "$plugin_name" "$full_version" "$shim_name" done } ================================================ FILE: lib/commands/version_commands.bash ================================================ # -*- sh -*- # shellcheck source=lib/functions/versions.bash . "$(dirname "$(dirname "$0")")/lib/functions/versions.bash" ================================================ FILE: lib/functions/installs.bash ================================================ handle_failure() { local install_path="$1" rm -rf "$install_path" exit 1 } handle_cancel() { local install_path="$1" printf "\nreceived sigint, cleaning up" handle_failure "$install_path" } install_command() { local plugin_name=$1 local full_version=$2 local extra_args="${*:3}" if [ "$plugin_name" = "" ] && [ "$full_version" = "" ]; then install_local_tool_versions "$extra_args" elif [[ $# -eq 1 ]]; then install_one_local_tool "$plugin_name" else install_tool_version "$plugin_name" "$full_version" "$extra_args" fi } get_concurrency() { local asdf_concurrency= if [ -n "$ASDF_CONCURRENCY" ]; then asdf_concurrency="$ASDF_CONCURRENCY" else 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() { local plugin_name=$1 local search_path search_path=$PWD local plugin_versions local plugin_version local plugin_version_and_path plugin_version_and_path="$(find_versions "$plugin_name" "$search_path")" if [ -n "$plugin_version_and_path" ]; then local plugin_version plugin_versions=$(cut -d '|' -f 1 <<<"$plugin_version_and_path") for plugin_version in $plugin_versions; do install_tool_version "$plugin_name" "$plugin_version" done else printf "No versions specified for %s in config files or environment\n" "$plugin_name" exit 1 fi } install_local_tool_versions() { local plugins_path plugins_path=$(get_plugin_path) local search_path search_path=$PWD local some_tools_installed local some_plugin_not_installed local tool_versions_path tool_versions_path=$(find_tool_versions) # Locate all the plugins installed in the system local plugins_installed if find "$plugins_path" -mindepth 1 -type d &>/dev/null; then for plugin_path in "$plugins_path"/*/; do local plugin_name plugin_name=$(basename "$plugin_path") plugins_installed="$plugins_installed $plugin_name" done plugins_installed=$(printf "%s" "$plugins_installed" | tr " " "\n") fi if [ -z "$plugins_installed" ]; then printf "Install plugins first to be able to install tools\n" exit 1 fi # Locate all the plugins defined in the versions file. local tools_file if [ -f "$tool_versions_path" ]; then tools_file=$(strip_tool_version_comments "$tool_versions_path" | cut -d ' ' -f 1) for plugin_name in $tools_file; do if ! printf '%s\n' "${plugins_installed[@]}" | grep -q "^$plugin_name\$"; then printf "%s plugin is not installed\n" "$plugin_name" some_plugin_not_installed='yes' fi done fi if [ -n "$some_plugin_not_installed" ]; then exit 1 fi if [ -n "$plugins_installed" ]; then for plugin_name in $plugins_installed; do local plugin_version_and_path plugin_version_and_path="$(find_versions "$plugin_name" "$search_path")" if [ -n "$plugin_version_and_path" ]; then local plugin_version some_tools_installed='yes' plugin_versions=$(cut -d '|' -f 1 <<<"$plugin_version_and_path") for plugin_version in $plugin_versions; do install_tool_version "$plugin_name" "$plugin_version" done fi done fi if [ -z "$some_tools_installed" ]; then printf "Either specify a tool & version in the command\n" printf "OR add .tool-versions file in this directory\n" printf "or in a parent directory\n" exit 1 fi } install_tool_version() { local plugin_name=$1 local full_version=$2 local flags=$3 local keep_download local plugin_path plugin_path=$(get_plugin_path "$plugin_name") check_if_plugin_exists "$plugin_name" for flag in $flags; do case "$flag" in "--keep-download") keep_download=true shift ;; *) shift ;; esac done if [ "$full_version" = "system" ]; then return fi IFS=':' read -r -a version_info <<<"$full_version" if [ "${version_info[0]}" = "ref" ]; then local install_type="${version_info[0]}" local version="${version_info[1]}" else local install_type="version" if [ "${version_info[0]}" = "latest" ]; then local version version=$(latest_command "$plugin_name" "${version_info[1]}") full_version=$version else local version="${version_info[0]}" fi fi local install_path install_path=$(get_install_path "$plugin_name" "$install_type" "$version") local download_path download_path=$(get_download_path "$plugin_name" "$install_type" "$version") local concurrency concurrency=$(get_concurrency) trap 'handle_cancel $install_path' INT if [ -d "$install_path" ]; then printf "%s %s is already installed\n" "$plugin_name" "$full_version" else if [ -f "${plugin_path}/bin/download" ]; then # Not a legacy plugin # Run the download script ( # shellcheck disable=SC2030 export ASDF_INSTALL_TYPE=$install_type # shellcheck disable=SC2030 export ASDF_INSTALL_VERSION=$version # shellcheck disable=SC2030 export ASDF_INSTALL_PATH=$install_path # shellcheck disable=SC2030 export ASDF_DOWNLOAD_PATH=$download_path mkdir -p "$download_path" asdf_run_hook "pre_asdf_download_${plugin_name}" "$full_version" "${plugin_path}"/bin/download ) fi local download_exit_code=$? if [ $download_exit_code -eq 0 ]; then ( # shellcheck disable=SC2031 export ASDF_INSTALL_TYPE=$install_type # shellcheck disable=SC2031 export ASDF_INSTALL_VERSION=$version # shellcheck disable=SC2031 export ASDF_INSTALL_PATH=$install_path # shellcheck disable=SC2031 export ASDF_DOWNLOAD_PATH=$download_path # shellcheck disable=SC2031 export ASDF_CONCURRENCY=$concurrency mkdir "$install_path" asdf_run_hook "pre_asdf_install_${plugin_name}" "$full_version" "${plugin_path}"/bin/install ) fi local install_exit_code=$? if [ $install_exit_code -eq 0 ] && [ $download_exit_code -eq 0 ]; then # If the download directory should be kept, but isn't available, warn the user always_keep_download=$(get_asdf_config_value "always_keep_download") if [ "$keep_download" = "true" ] || [ "$always_keep_download" = "yes" ]; then if [ ! -d "$download_path" ]; then printf '%s\n' "asdf: Warn: You have configured asdf to preserve downloaded files (with always_keep_download=yes or --keep-download). But" >&2 printf '%s\n' "asdf: Warn: the current plugin ($plugin_name) does not support that. Downloaded files will not be preserved." >&2 fi # Otherwise, remove the download directory if it exists elif [ -d "$download_path" ]; then rm -rf "$download_path" fi reshim_command "$plugin_name" "$full_version" asdf_run_hook "post_asdf_install_${plugin_name}" "$full_version" else handle_failure "$install_path" fi fi } ================================================ FILE: lib/functions/plugins.bash ================================================ plugin_list_command() { local plugins_path plugins_path=$(get_plugin_path) local show_repo local show_ref while [ -n "$*" ]; do case "$1" in "--urls") show_repo=true shift ;; "--refs") show_ref=true shift ;; *) shift ;; esac done if find "$plugins_path" -mindepth 1 -type d &>/dev/null; then ( for plugin_path in "$plugins_path"/*/; do plugin_name=$(basename "$plugin_path") printf "%s" "$plugin_name" if [ -n "$show_repo" ]; then printf "\t%s" "$(get_plugin_remote_url "$plugin_name")" fi if [ -n "$show_ref" ]; then printf "\t%s\t%s" \ "$(get_plugin_remote_branch "$plugin_name")" \ "$(get_plugin_remote_gitref "$plugin_name")" fi printf "\n" done ) | awk '{ if (NF > 1) { printf("%-28s", $1) ; $1="" }; print $0}' else display_error 'No plugins installed' exit 0 fi } plugin_add_command() { if [[ $# -lt 1 || $# -gt 2 ]]; then display_error "usage: asdf plugin add <name> [<git-url>]" exit 1 fi local plugin_name=$1 local regex="^[[:lower:][:digit:]_-]+$" if ! printf "%s" "$plugin_name" | grep -q -E "$regex"; then display_error "$plugin_name is invalid. Name may only contain lowercase letters, numbers, '_', and '-'" exit 1 fi if [ -n "$2" ]; then local source_url=$2 else initialize_or_update_plugin_repository local source_url source_url=$(get_plugin_source_url "$plugin_name") fi if [ -z "$source_url" ]; then display_error "plugin $plugin_name not found in repository" exit 1 fi local plugin_path plugin_path=$(get_plugin_path "$plugin_name") [ -d "$(asdf_data_dir)/plugins" ] || mkdir -p "$(asdf_data_dir)/plugins" if [ -d "$plugin_path" ]; then printf '%s\n' "Plugin named $plugin_name already added" exit 0 else asdf_run_hook "pre_asdf_plugin_add" "$plugin_name" asdf_run_hook "pre_asdf_plugin_add_${plugin_name}" if ! git clone -q "$source_url" "$plugin_path"; then exit 1 fi if [ -f "${plugin_path}/bin/post-plugin-add" ]; then ( export ASDF_PLUGIN_SOURCE_URL=$source_url # shellcheck disable=SC2030 export ASDF_PLUGIN_PATH=$plugin_path "${plugin_path}/bin/post-plugin-add" ) fi asdf_run_hook "post_asdf_plugin_add" "$plugin_name" asdf_run_hook "post_asdf_plugin_add_${plugin_name}" fi } plugin_update_command() { if [ "$#" -lt 1 ]; then display_error "usage: asdf plugin-update {<name> [git-ref] | --all}" exit 1 fi local plugin_name="$1" local gitref="${2}" local plugins= if [ "$plugin_name" = "--all" ]; then if [ -d "$(asdf_data_dir)"/plugins ]; then plugins=$(find "$(asdf_data_dir)"/plugins -mindepth 1 -maxdepth 1 -type d) while IFS= read -r dir; do update_plugin "$(basename "$dir")" "$dir" "$gitref" & done <<<"$plugins" wait fi else local plugin_path plugin_path="$(get_plugin_path "$plugin_name")" check_if_plugin_exists "$plugin_name" update_plugin "$plugin_name" "$plugin_path" "$gitref" fi } update_plugin() { local plugin_name=$1 local plugin_path=$2 plugin_remote_default_branch=$(git --git-dir "$plugin_path/.git" --work-tree "$plugin_path" ls-remote --symref origin HEAD | awk '{ sub(/refs\/heads\//, ""); print $2; exit }') local gitref=${3:-${plugin_remote_default_branch}} logfile=$(mktemp) local common_git_options=(--git-dir "$plugin_path/.git" --work-tree "$plugin_path") local prev_ref= local post_ref= { printf "Location of %s plugin: %s\n" "$plugin_name" "$plugin_path" asdf_run_hook "pre_asdf_plugin_update" "$plugin_name" asdf_run_hook "pre_asdf_plugin_update_${plugin_name}" printf "Updating %s to %s\n" "$plugin_name" "$gitref" git "${common_git_options[@]}" fetch --prune --update-head-ok origin "$gitref:$gitref" prev_ref=$(git "${common_git_options[@]}" rev-parse --short HEAD) post_ref=$(git "${common_git_options[@]}" rev-parse --short "${gitref}") git "${common_git_options[@]}" -c advice.detachedHead=false checkout --force "$gitref" if [ -f "${plugin_path}/bin/post-plugin-update" ]; then ( # shellcheck disable=SC2031 export ASDF_PLUGIN_PATH=$plugin_path export ASDF_PLUGIN_PREV_REF=$prev_ref export ASDF_PLUGIN_POST_REF=$post_ref "${plugin_path}/bin/post-plugin-update" ) fi asdf_run_hook "post_asdf_plugin_update" "$plugin_name" asdf_run_hook "post_asdf_plugin_update_${plugin_name}" } >"$logfile" 2>&1 cat "$logfile" rm "$logfile" } ================================================ FILE: lib/functions/versions.bash ================================================ version_command() { local cmd=$1 local plugin_name=$2 if [ "$#" -lt "3" ]; then if [ "$cmd" = "global" ]; then printf "Usage: asdf global <name> <version>\n" else printf "Usage: asdf local <name> <version>\n" fi exit 1 fi shift 2 local versions=("$@") local file_name local file file_name="$(asdf_tool_versions_filename)" if [ "$cmd" = "global" ]; then file="$HOME/$file_name" elif [ "$cmd" = "local-tree" ]; then file=$(find_tool_versions) else # cmd = local file="$PWD/$file_name" fi if [ -L "$file" ]; then # Resolve file path if symlink file="$(resolve_symlink "$file")" fi check_if_plugin_exists "$plugin_name" declare -a resolved_versions local item for item in "${!versions[@]}"; do IFS=':' read -r -a version_info <<<"${versions[$item]}" if [ "${version_info[0]}" = "latest" ] && [ -n "${version_info[1]}" ]; then version=$(latest_command "$plugin_name" "${version_info[1]}") elif [ "${version_info[0]}" = "latest" ] && [ -z "${version_info[1]}" ]; then version=$(latest_command "$plugin_name") else # if branch handles ref: || path: || normal versions version="${versions[$item]}" fi # check_if_version_exists should probably handle if either param is empty string if [ -z "$version" ]; then exit 1 fi if ! (check_if_version_exists "$plugin_name" "$version"); then version_not_installed_text "$plugin_name" "$version" 1>&2 exit 1 fi resolved_versions+=("$version") done if [ -f "$file" ] && grep -q "^$plugin_name " "$file"; then local temp_dir temp_dir=${TMPDIR:-/tmp} local temp_tool_versions_file temp_tool_versions_file=$(mktemp "$temp_dir/asdf-tool-versions-file.XXXXXX") cp -f "$file" "$temp_tool_versions_file" sed -e "s|^$plugin_name .*$|$plugin_name ${resolved_versions[*]}|" "$temp_tool_versions_file" >"$file" rm -f "$temp_tool_versions_file" else # Add a trailing newline at the end of the file if missing [[ -f "$file" && -n "$(tail -c1 "$file")" ]] && printf '\n' >>"$file" # Add a new version line to the end of the file printf "%s %s\n" "$plugin_name" "${resolved_versions[*]}" >>"$file" fi } list_all_command() { local plugin_name=$1 local query=$2 local plugin_path local std_out_file local std_err_file local output plugin_path=$(get_plugin_path "$plugin_name") check_if_plugin_exists "$plugin_name" local temp_dir temp_dir=${TMPDIR:-/tmp} # Capture return code to allow error handling std_out_file="$(mktemp "$temp_dir/asdf-command-list-all-${plugin_name}.stdout.XXXXXX")" std_err_file="$(mktemp "$temp_dir/asdf-command-list-all-${plugin_name}.stderr.XXXXXX")" return_code=0 && "${plugin_path}/bin/list-all" >"$std_out_file" 2>"$std_err_file" || return_code=$? if [[ $return_code -ne 0 ]]; then # Printing all output to allow plugin to handle error formatting printf "Plugin %s's list-all callback script failed with output:\n" "${plugin_name}" >&2 printf "%s\n" "$(cat "$std_err_file")" >&2 printf "%s\n" "$(cat "$std_out_file")" >&2 rm "$std_out_file" "$std_err_file" exit 1 fi if [[ $query ]]; then output=$(tr ' ' '\n' <"$std_out_file" | grep -E "^\\s*$query" | tr '\n' ' ') else output=$(cat "$std_out_file") fi if [ -z "$output" ]; then display_error "No compatible versions available ($plugin_name $query)" exit 1 fi IFS=' ' read -r -a versions_list <<<"$output" for version in "${versions_list[@]}"; do printf "%s\n" "${version}" done # Remove temp files if they still exist rm "$std_out_file" "$std_err_file" || true } latest_command() { DEFAULT_QUERY="[0-9]" local plugin_name=$1 local query=$2 local plugin_path if [ "$plugin_name" = "--all" ]; then latest_all fi [[ -z $query ]] && query="$DEFAULT_QUERY" plugin_path=$(get_plugin_path "$plugin_name") check_if_plugin_exists "$plugin_name" local versions if [ -f "${plugin_path}/bin/latest-stable" ]; then versions=$("${plugin_path}"/bin/latest-stable "$query") if [ -z "${versions}" ]; then # this branch requires this print to mimic the error from the list-all branch printf "No compatible versions available (%s %s)\n" "$plugin_name" "$query" >&2 exit 1 fi else # pattern from xxenv-latest (https://github.com/momo-lab/xxenv-latest) versions=$(list_all_command "$plugin_name" "$query" | grep -ivE "(^Available versions:|-src|-dev|-latest|-stm|[-\\.]rc|-milestone|-alpha|-beta|[-\\.]pre|-next|(a|b|c)[0-9]+|snapshot|master)" | sed 's/^[[:space:]]\+//' | tail -1) if [ -z "${versions}" ]; then exit 1 fi fi printf "%s\n" "$versions" } latest_all() { local plugins_path plugins_path=$(get_plugin_path) if find "$plugins_path" -mindepth 1 -type d &>/dev/null; then for plugin_path in "$plugins_path"/*/; do plugin_name=$(basename "$plugin_path") # Retrieve the version of the plugin local version if [ -f "${plugin_path}/bin/latest-stable" ]; then # We can't filter by a concrete query because different plugins might # have different queries. version=$("${plugin_path}"/bin/latest-stable "") if [ -z "${version}" ]; then version="unknown" fi else # pattern from xxenv-latest (https://github.com/momo-lab/xxenv-latest) version=$(list_all_command "$plugin_name" | grep -ivE "(^Available version:|-src|-dev|-latest|-stm|[-\\.]rc|-alpha|-beta|[-\\.]pre|-next|(a|b|c)[0-9]+|snapshot|master)" | sed 's/^[[:space:]]\+//' | tail -1) if [ -z "${version}" ]; then version="unknown" fi fi local installed_status installed_status="missing" local installed_versions installed_versions=$(list_installed_versions "$plugin_name") if [ -n "$installed_versions" ] && printf '%s\n' "$installed_versions" | grep -q "^$version\$"; then installed_status="installed" fi printf "%s\t%s\t%s\n" "$plugin_name" "$version" "$installed_status" done else printf "%s\n" 'No plugins installed' fi exit 0 } local_command() { local parent=false local positional=() while [[ $# -gt 0 ]]; do case $1 in -p | --parent) parent="true" shift # past value ;; *) positional+=("$1") # save it in an array for later shift # past argument ;; esac done set -- "${positional[@]}" # restore positional parameters if [ $parent = true ]; then version_command local-tree "$@" else version_command local "$@" fi } ================================================ FILE: lib/utils.bash ================================================ # We shouldn't rely on the user's grep settings to be correct. If we set these # here anytime asdf invokes grep it will be invoked with these options # shellcheck disable=SC2034 GREP_OPTIONS="--color=never" # shellcheck disable=SC2034 GREP_COLORS= asdf_version() { local version git_rev version="v$(cat "$(asdf_dir)/version.txt")" if [ -d "$(asdf_dir)/.git" ]; then git_rev="$(git --git-dir "$(asdf_dir)/.git" rev-parse --short HEAD)" printf "%s-%s\n" "$version" "$git_rev" else printf "%s\n" "$version" fi } asdf_tool_versions_filename() { printf '%s\n' "${ASDF_DEFAULT_TOOL_VERSIONS_FILENAME:-.tool-versions}" } asdf_config_file() { printf '%s\n' "${ASDF_CONFIG_FILE:-$HOME/.asdfrc}" } asdf_data_dir() { local data_dir if [ -n "${ASDF_DATA_DIR}" ]; then data_dir="${ASDF_DATA_DIR}" elif [ -n "$HOME" ]; then data_dir="$HOME/.asdf" else data_dir=$(asdf_dir) fi 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 local version=$3 local install_dir install_dir="$(asdf_data_dir)/installs" [ -d "${install_dir}/${plugin}" ] || mkdir -p "${install_dir}/${plugin}" if [ "$install_type" = "version" ]; then printf "%s/%s/%s\n" "$install_dir" "$plugin" "$version" elif [ "$install_type" = "path" ]; then printf "%s\n" "$version" else printf "%s/%s/%s-%s\n" "$install_dir" "$plugin" "$install_type" "$version" fi } get_download_path() { local plugin=$1 local install_type=$2 local version=$3 local download_dir download_dir="$(asdf_data_dir)/downloads" [ -d "${download_dir}/${plugin}" ] || mkdir -p "${download_dir}/${plugin}" if [ "$install_type" = "version" ]; then printf "%s/%s/%s\n" "$download_dir" "$plugin" "$version" elif [ "$install_type" = "path" ]; then return else printf "%s/%s/%s-%s\n" "$download_dir" "$plugin" "$install_type" "$version" fi } list_installed_versions() { local plugin_name=$1 local plugin_path plugin_path=$(get_plugin_path "$plugin_name") local plugin_installs_path plugin_installs_path="$(asdf_data_dir)/installs/${plugin_name}" if [ -d "$plugin_installs_path" ]; then for install in "${plugin_installs_path}"/*/; do [[ -e "$install" ]] || break basename "$install" | sed 's/^ref-/ref:/' done fi } check_if_plugin_exists() { local plugin_name=$1 # Check if we have a non-empty argument if [ -z "${1}" ]; then display_error "No plugin given" exit 1 fi if [ ! -d "$(asdf_data_dir)/plugins/$plugin_name" ]; then display_error "No such plugin: $plugin_name" exit 1 fi } check_if_version_exists() { local plugin_name=$1 local version=$2 check_if_plugin_exists "$plugin_name" local install_path install_path=$(find_install_path "$plugin_name" "$version") if [ "$version" != "system" ] && [ ! -d "$install_path" ]; then exit 1 fi } version_not_installed_text() { local plugin_name=$1 local version=$2 printf "version %s is not installed for %s\n" "$version" "$plugin_name" } get_plugin_path() { if [ -n "$1" ]; then printf "%s\n" "$(asdf_data_dir)/plugins/$1" else printf "%s\n" "$(asdf_data_dir)/plugins" fi } display_error() { printf "%s\n" "$1" >&2 } get_version_in_dir() { local plugin_name=$1 local search_path=$2 local legacy_filenames=$3 local asdf_version file_name=$(asdf_tool_versions_filename) asdf_version=$(parse_asdf_version_file "$search_path/$file_name" "$plugin_name") if [ -n "$asdf_version" ]; then printf "%s\n" "$asdf_version|$search_path/$file_name" return 0 fi for filename in $legacy_filenames; do local legacy_version legacy_version=$(parse_legacy_version_file "$search_path/$filename" "$plugin_name") if [ -n "$legacy_version" ]; then printf "%s\n" "$legacy_version|$search_path/$filename" return 0 fi done } find_versions() { local plugin_name=$1 local search_path=$2 local version version=$(get_version_from_env "$plugin_name") if [ -n "$version" ]; then local upcase_name upcase_name=$(printf "%s\n" "$plugin_name" | tr '[:lower:]-' '[:upper:]_') local version_env_var="ASDF_${upcase_name}_VERSION" printf "%s\n" "$version|$version_env_var environment variable" return 0 fi local plugin_path plugin_path=$(get_plugin_path "$plugin_name") local legacy_config legacy_config=$(get_asdf_config_value "legacy_version_file") local legacy_list_filenames_script legacy_list_filenames_script="${plugin_path}/bin/list-legacy-filenames" local legacy_filenames="" if [ "$legacy_config" = "yes" ] && [ -f "$legacy_list_filenames_script" ]; then legacy_filenames=$("$legacy_list_filenames_script") fi while [ "$search_path" != "/" ]; do version=$(get_version_in_dir "$plugin_name" "$search_path" "$legacy_filenames") if [ -n "$version" ]; then printf "%s\n" "$version" return 0 fi search_path=$(dirname "$search_path") done get_version_in_dir "$plugin_name" "$HOME" "$legacy_filenames" if [ -f "$ASDF_DEFAULT_TOOL_VERSIONS_FILENAME" ]; then versions=$(parse_asdf_version_file "$ASDF_DEFAULT_TOOL_VERSIONS_FILENAME" "$plugin_name") if [ -n "$versions" ]; then printf "%s\n" "$versions|$ASDF_DEFAULT_TOOL_VERSIONS_FILENAME" return 0 fi fi } display_no_version_set() { local plugin_name=$1 printf "No version is set for %s; please run \`asdf <global | shell | local> %s <version>\`\n" "$plugin_name" "$plugin_name" } get_version_from_env() { local plugin_name=$1 local upcase_name upcase_name=$(printf "%s\n" "$plugin_name" | tr '[:lower:]-' '[:upper:]_') local version_env_var="ASDF_${upcase_name}_VERSION" local version=${!version_env_var:-} printf "%s\n" "$version" } find_install_path() { local plugin_name=$1 local version=$2 # shellcheck disable=SC2162 IFS=':' read -a version_info <<<"$version" if [ "$version" = "system" ]; then printf "\n" elif [ "${version_info[0]}" = "ref" ]; then local install_type="${version_info[0]}" local version="${version_info[1]}" get_install_path "$plugin_name" "$install_type" "$version" elif [ "${version_info[0]}" = "path" ]; then # This is for people who have the local source already compiled # Like those who work on the language, etc # We'll allow specifying path:/foo/bar/project in .tool-versions # And then use the binaries there local install_type="path" local version="path" util_resolve_user_path "${version_info[1]}" printf "%s\n" "${util_resolve_user_path_reply}" else local install_type="version" local version="${version_info[0]}" get_install_path "$plugin_name" "$install_type" "$version" fi } get_custom_executable_path() { local plugin_path=$1 local install_path=$2 local executable_path=$3 # custom plugin hook for executable path if [ -x "${plugin_path}/bin/exec-path" ]; then cmd=$(basename "$executable_path") local relative_path # shellcheck disable=SC2001 relative_path=$(printf "%s\n" "$executable_path" | sed -e "s|${install_path}/||") relative_path="$("${plugin_path}/bin/exec-path" "$install_path" "$cmd" "$relative_path")" executable_path="$install_path/$relative_path" fi printf "%s\n" "$executable_path" } get_executable_path() { local plugin_name=$1 local version=$2 local executable_path=$3 check_if_version_exists "$plugin_name" "$version" if [ "$version" = "system" ]; then path=$(remove_path_from_path "$PATH" "$(asdf_data_dir)/shims") cmd=$(basename "$executable_path") cmd_path=$(PATH=$path command -v "$cmd" 2>&1) # shellcheck disable=SC2181 if [ $? -ne 0 ]; then return 1 fi printf "%s\n" "$cmd_path" else local install_path install_path=$(find_install_path "$plugin_name" "$version") printf "%s\n" "${install_path}"/"${executable_path}" fi } parse_asdf_version_file() { local file_path=$1 local plugin_name=$2 if [ -f "$file_path" ]; then local version version=$(strip_tool_version_comments "$file_path" | grep "^${plugin_name} " | sed -e "s/^${plugin_name} //") if [ -n "$version" ]; then if [[ "$version" == path:* ]]; then util_resolve_user_path "${version#path:}" printf "%s\n" "path:${util_resolve_user_path_reply}" else printf "%s\n" "$version" fi return 0 fi fi } parse_legacy_version_file() { local file_path=$1 local plugin_name=$2 local plugin_path plugin_path=$(get_plugin_path "$plugin_name") local parse_legacy_script parse_legacy_script="${plugin_path}/bin/parse-legacy-file" if [ -f "$file_path" ]; then if [ -f "$parse_legacy_script" ]; then "$parse_legacy_script" "$file_path" else cat "$file_path" fi fi } get_preset_version_for() { local plugin_name=$1 local search_path search_path=$PWD local version_and_path version_and_path=$(find_versions "$plugin_name" "$search_path") local version version=$(cut -d '|' -f 1 <<<"$version_and_path") printf "%s\n" "$version" } get_asdf_config_value_from_file() { local config_path=$1 local key=$2 if [ ! -f "$config_path" ]; then 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 printf "%s\n" "$result" return 0 fi return 2 } get_asdf_config_value() { local key=$1 local config_path= config_path=$(asdf_config_file) local default_config_path=${ASDF_CONFIG_DEFAULT_FILE:-"$(asdf_dir)/defaults"} local local_config_path local_config_path="$(find_file_upwards ".asdfrc")" get_asdf_config_value_from_file "$local_config_path" "$key" || get_asdf_config_value_from_file "$config_path" "$key" || get_asdf_config_value_from_file "$default_config_path" "$key" } # Whether the plugin shortname repo needs to be synced # 0: if no sync needs to occur # 1: if sync needs to occur repository_needs_update() { local plugin_repository_last_check_duration local sync_required plugin_repository_last_check_duration="$(get_asdf_config_value "plugin_repository_last_check_duration")" if [ "never" != "$plugin_repository_last_check_duration" ]; then local update_file_dir local update_file_name update_file_dir="$(asdf_data_dir)/tmp" update_file_name="repo-updated" # `find` outputs filename if it has not been modified in plugin_repository_last_check_duration setting. sync_required=$(find "$update_file_dir" -name "$update_file_name" -type f -mmin +"${plugin_repository_last_check_duration:-60}" -print) fi [ "$sync_required" ] } initialize_or_update_plugin_repository() { local repository_url local repository_path disable_plugin_short_name_repo="$(get_asdf_config_value "disable_plugin_short_name_repository")" if [ "yes" = "$disable_plugin_short_name_repo" ]; then printf "Short-name plugin repository is disabled\n" >&2 exit 1 fi repository_url=$(asdf_plugin_repository_url) repository_path=$(asdf_data_dir)/repository if [ ! -d "$repository_path" ]; then printf "initializing plugin repository..." git clone "$repository_url" "$repository_path" elif repository_needs_update; then printf "updating plugin repository..." git -C "$repository_path" fetch git -C "$repository_path" reset --hard origin/master fi [ -d "$(asdf_data_dir)/tmp" ] || mkdir -p "$(asdf_data_dir)/tmp" touch "$(asdf_data_dir)/tmp/repo-updated" } get_plugin_source_url() { local plugin_name=$1 local plugin_config plugin_config="$(asdf_data_dir)/repository/plugins/$plugin_name" if [ -f "$plugin_config" ]; then grep "repository" "$plugin_config" | awk -F'=' '{print $2}' | sed 's/ //' fi } find_tool_versions() { find_file_upwards "$(asdf_tool_versions_filename)" } find_file_upwards() { local name="$1" local search_path 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 search_path=$(dirname "$search_path") done } resolve_symlink() { local symlink symlink="$1" # This seems to be the only cross-platform way to resolve symlink paths to # the real file path. # shellcheck disable=SC2012 resolved_path=$(ls -l "$symlink" | sed -e 's|.*-> \(.*\)|\1|') # asdf_allow: ls ' # Check if resolved path is relative or not by looking at the first character. # If it is a slash we can assume it's root and absolute. Otherwise we treat it # as relative case $resolved_path in /*) printf "%s\n" "$resolved_path" ;; *) ( cd "$(dirname "$symlink")" || exit 1 printf "%s\n" "$PWD/$resolved_path" ) ;; esac } list_plugin_bin_paths() { local plugin_name=$1 local version=$2 local install_type=$3 local plugin_path plugin_path=$(get_plugin_path "$plugin_name") local install_path install_path=$(get_install_path "$plugin_name" "$install_type" "$version") if [ -f "${plugin_path}/bin/list-bin-paths" ]; then local space_separated_list_of_bin_paths # shellcheck disable=SC2030 space_separated_list_of_bin_paths=$( export ASDF_INSTALL_TYPE=$install_type export ASDF_INSTALL_VERSION=$version export ASDF_INSTALL_PATH=$install_path "${plugin_path}/bin/list-bin-paths" ) else local space_separated_list_of_bin_paths="bin" fi printf "%s\n" "$space_separated_list_of_bin_paths" } list_plugin_exec_paths() { local plugin_name=$1 local full_version=$2 check_if_plugin_exists "$plugin_name" IFS=':' read -r -a version_info <<<"$full_version" if [ "${version_info[0]}" = "ref" ]; then local install_type="${version_info[0]}" local version="${version_info[1]}" elif [ "${version_info[0]}" = "path" ]; then local install_type="${version_info[0]}" local version="${version_info[1]}" else local install_type="version" local version="${version_info[0]}" fi local plugin_shims_path plugin_shims_path=$(get_plugin_path "$plugin_name")/shims if [ -d "$plugin_shims_path" ]; then printf "%s\n" "$plugin_shims_path" fi space_separated_list_of_bin_paths="$(list_plugin_bin_paths "$plugin_name" "$version" "$install_type")" IFS=' ' read -r -a all_bin_paths <<<"$space_separated_list_of_bin_paths" local install_path install_path=$(get_install_path "$plugin_name" "$install_type" "$version") for bin_path in "${all_bin_paths[@]}"; do printf "%s\n" "$install_path/$bin_path" done } with_plugin_env() { local plugin_name=$1 local full_version=$2 local callback=$3 IFS=':' read -r -a version_info <<<"$full_version" if [ "${version_info[0]}" = "ref" ]; then local install_type="${version_info[0]}" local version="${version_info[1]}" else local install_type="version" local version="${version_info[0]}" fi if [ "$version" = "system" ]; then # execute as is for system "$callback" return $? fi local plugin_path plugin_path=$(get_plugin_path "$plugin_name") # add the plugin listed exec paths to PATH local path exec_paths exec_paths="$(list_plugin_exec_paths "$plugin_name" "$full_version")" # exec_paths contains a trailing newline which is converted to a colon, so no # colon is needed between the subshell and the PATH variable in this string path="$(tr '\n' ':' <<<"$exec_paths")$PATH" # If no custom exec-env transform, just execute callback if [ ! -f "${plugin_path}/bin/exec-env" ]; then PATH=$path "$callback" return $? fi # Load the plugin custom environment local install_path install_path=$(find_install_path "$plugin_name" "$full_version") # shellcheck source=/dev/null ASDF_INSTALL_TYPE=$install_type \ ASDF_INSTALL_VERSION=$version \ ASDF_INSTALL_PATH=$install_path \ . "${plugin_path}/bin/exec-env" PATH=$path "$callback" } plugin_executables() { local plugin_name=$1 local full_version=$2 local all_bin_paths IFS=$'\n' read -rd '' -a all_bin_paths <<<"$(list_plugin_exec_paths "$plugin_name" "$full_version")" for bin_path in "${all_bin_paths[@]}"; do for executable_file in "$bin_path"/*; do if is_executable "$executable_file"; then printf "%s\n" "$executable_file" fi done done } is_executable() { local executable_path=$1 if [[ (-f "$executable_path") && (-x "$executable_path") ]]; then return 0 fi return 1 } plugin_shims() { local plugin_name=$1 local full_version=$2 grep -lx "# asdf-plugin: $plugin_name $full_version" "$(asdf_data_dir)/shims"/* 2>/dev/null } shim_plugin_versions() { local executable_name executable_name=$(basename "$1") local shim_path shim_path="$(asdf_data_dir)/shims/${executable_name}" if [ -x "$shim_path" ]; then grep "# asdf-plugin: " "$shim_path" 2>/dev/null | sed -e "s/# asdf-plugin: //" | uniq else printf "asdf: unknown shim %s\n" "$executable_name" return 1 fi } shim_plugins() { local executable_name executable_name=$(basename "$1") local shim_path shim_path="$(asdf_data_dir)/shims/${executable_name}" if [ -x "$shim_path" ]; then grep "# asdf-plugin: " "$shim_path" 2>/dev/null | sed -e "s/# asdf-plugin: //" | cut -d' ' -f 1 | uniq else printf "asdf: unknown shim %s\n" "$executable_name" return 1 fi } strip_tool_version_comments() { local tool_version_path="$1" # Use sed to strip comments from the tool version file # Breakdown of sed command: # This command represents 3 steps, separated by a semi-colon (;), that run on each line. # 1. Delete line if it starts with any blankspace and a #. # 2. Find a # and delete it and everything after the #. # 3. Remove any whitespace from the end of the line. # Finally, the command will print the lines that are not empty. sed '/^[[:blank:]]*#/d;s/#.*//;s/[[:blank:]]*$//' "$tool_version_path" } asdf_run_hook() { local hook_name=$1 local hook_cmd hook_cmd="$(get_asdf_config_value "$hook_name")" if [ -n "$hook_cmd" ]; then asdf_hook_fun() { unset asdf_hook_fun ev'al' "$hook_cmd" # ignore banned command just here } asdf_hook_fun "${@:2}" fi } get_shim_versions() { shim_name=$1 shim_plugin_versions "${shim_name}" shim_plugin_versions "${shim_name}" | cut -d' ' -f 1 | awk '{print$1" system"}' } preset_versions() { shim_name=$1 shim_plugin_versions "${shim_name}" | cut -d' ' -f 1 | uniq | xargs -IPLUGIN bash -c ". $(asdf_dir)/lib/utils.bash; printf \"%s %s\n\" PLUGIN \$(get_preset_version_for PLUGIN)" } select_from_preset_version() { local shim_name=$1 local shim_versions local preset_versions shim_versions=$(get_shim_versions "$shim_name") if [ -n "$shim_versions" ]; then preset_versions=$(preset_versions "$shim_name") grep -F "$shim_versions" <<<"$preset_versions" | head -n 1 | xargs -IVERSION printf "%s\n" VERSION fi } select_version() { shim_name=$1 # First, we get the all the plugins where the # current shim is available. # Then, we iterate on all versions set for each plugin # Note that multiple plugin versions can be set for a single plugin. # These are separated by a space. e.g. python 3.7.2 2.7.15 # For each plugin/version pair, we check if it is present in the shim local search_path search_path=$PWD local shim_versions IFS=$'\n' read -rd '' -a shim_versions <<<"$(get_shim_versions "$shim_name")" local plugins IFS=$'\n' read -rd '' -a plugins <<<"$(shim_plugins "$shim_name")" for plugin_name in "${plugins[@]}"; do local version_and_path local version_string local usable_plugin_versions local _path version_and_path=$(find_versions "$plugin_name" "$search_path") IFS='|' read -r version_string _path <<<"$version_and_path" IFS=' ' read -r -a usable_plugin_versions <<<"$version_string" for plugin_version in "${usable_plugin_versions[@]}"; do for plugin_and_version in "${shim_versions[@]}"; do local plugin_shim_name local plugin_shim_version IFS=' ' read -r plugin_shim_name plugin_shim_version <<<"$plugin_and_version" if [[ "$plugin_name" == "$plugin_shim_name" ]]; then if [[ "$plugin_version" == "$plugin_shim_version" ]]; then printf "%s\n" "$plugin_name $plugin_version" return elif [[ "$plugin_version" == "path:"* ]]; then printf "%s\n" "$plugin_name $plugin_version" return fi fi done done done } with_shim_executable() { local shim_name shim_name=$(basename "$1") local shim_exec="${2}" if [ ! -f "$(asdf_data_dir)/shims/${shim_name}" ]; then printf "%s %s %s\n" "unknown command:" "${shim_name}." "Perhaps you have to reshim?" >&2 return 1 fi local selected_version selected_version="$(select_version "$shim_name")" if [ -z "$selected_version" ]; then selected_version="$(select_from_preset_version "$shim_name")" fi if [ -n "$selected_version" ]; then local plugin_name local full_version local plugin_path IFS=' ' read -r plugin_name full_version <<<"$selected_version" plugin_path=$(get_plugin_path "$plugin_name") # This function does get invoked, but shellcheck sees it as unused code # shellcheck disable=SC2317 run_within_env() { local path path=$(remove_path_from_path "$PATH" "$(asdf_data_dir)/shims") executable_path=$(PATH=$path command -v "$shim_name") if [ -x "${plugin_path}/bin/exec-path" ]; then install_path=$(find_install_path "$plugin_name" "$full_version") executable_path=$(get_custom_executable_path "${plugin_path}" "${install_path}" "${executable_path:-${shim_name}}") fi "$shim_exec" "$plugin_name" "$full_version" "$executable_path" } with_plugin_env "$plugin_name" "$full_version" run_within_env return $? fi ( local preset_plugin_versions preset_plugin_versions=() local closest_tool_version closest_tool_version=$(find_tool_versions) local shim_plugins IFS=$'\n' read -rd '' -a shim_plugins <<<"$(shim_plugins "$shim_name")" for shim_plugin in "${shim_plugins[@]}"; do local shim_versions local version_string version_string=$(get_preset_version_for "$shim_plugin") IFS=' ' read -r -a shim_versions <<<"$version_string" local usable_plugin_versions for shim_version in "${shim_versions[@]}"; do preset_plugin_versions+=("$shim_plugin $shim_version") done done if [ -n "${preset_plugin_versions[*]}" ]; then printf "%s %s\n" "No preset version installed for command" "$shim_name" printf "%s\n\n" "Please install a version by running one of the following:" for preset_plugin_version in "${preset_plugin_versions[@]}"; do printf "%s %s\n" "asdf install" "$preset_plugin_version" done printf "\n%s %s\n" "or add one of the following versions in your config file at" "$closest_tool_version" else printf "%s %s\n" "No version is set for command" "$shim_name" printf "%s %s\n" "Consider adding one of the following versions in your config file at" "$closest_tool_version" fi shim_plugin_versions "${shim_name}" ) >&2 return 126 } substitute() { # Use Bash substitution rather than sed as it will handle escaping of all # strings for us. local input=$1 local find_str=$2 local replace=$3 printf "%s" "${input//"$find_str"/"$replace"}" } remove_path_from_path() { # A helper function for removing an arbitrary path from the PATH variable. # Output is a new string suitable for assignment to PATH local PATH=$1 local path=$2 substitute "$PATH" "$path" "" | sed -e "s|::|:|g" } # @description Strings that began with a ~ are always paths. In # that case, then ensure ~ it handled like a shell util_resolve_user_path() { util_resolve_user_path_reply= local path="$1" # shellcheck disable=SC2088 if [ "${path::2}" = '~/' ]; then util_resolve_user_path_reply="${HOME}/${path:2}" else 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 } ================================================ FILE: release-please-config.json ================================================ { "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", "packages": { ".": { "release-type": "go", "bump-minor-pre-major": true, "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", "docs/pt-br/guide/getting-started.md", "docs/zh-hans/guide/getting-started.md", "docs/ko-kr/guide/getting-started.md", "docs/ja-jp/guide/getting-started.md", "cmd/asdf/main.go" ] } } } ================================================ FILE: scripts/checkstyle.py ================================================ #!/usr/bin/env python3 import re import os import argparse from pathlib import Path from typing import Callable, List, Dict, Any # compat # 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, .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: # $ ./scripts/checkstyle.py --fix # # Lint a particular file: # $ ./scripts/checkstyle.py ./lib/functions/installs.bash # # Check to ensure all regular expressions are working as intended: # $ ./scripts/checkstyle.py --internal-test-regex Rule = Dict[str, Any] class c: RED = '\033[91m' GREEN = '\033[92m' YELLOW = '\033[93m' BLUE = '\033[94m' MAGENTA = '\033[95m' CYAN = '\033[96m' RESET = '\033[0m' BOLD = '\033[1m' UNDERLINE = '\033[4m' LINK: Callable[[str, str], str] = lambda href, text: f'\033]8;;{href}\a{text}\033]8;;\a' def utilGetStrs(line: Any, m: Any): return ( line[0:m.start('match')], line[m.start('match'):m.end('match')], line[m.end('match'):] ) # Before: printf '%s\\n' '^w^' # After: printf '%s\n' '^w^' 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: Any) -> str: prestr, _, poststr = utilGetStrs(line, m) return f'{prestr}$PWD{poststr}' # Before: [ a == b ] # After: [ a = b ] def noTestDoubleEqualsFixer(line: str, m: Any) -> str: prestr, _, poststr = utilGetStrs(line, m) return f'{prestr}={poststr}' # Before: function fn() { ... # After: fn() { ... # --- # Before: function fn { ... # After fn() { ... def noFunctionKeywordFixer(line: str, m: Any) -> str: prestr, midstr, poststr = utilGetStrs(line, m) midstr = midstr.strip() midstr = midstr[len('function'):] midstr = midstr.strip() parenIdx = midstr.find('(') if parenIdx != -1: midstr = midstr[:parenIdx] return f'{prestr}{midstr}() {poststr}' # 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(file.resolve(), Path.cwd()) prestr = line[0:m.start('match')] midstr = line[m.start('match'):m.end('match')] poststr = line[m.end('match'):] print(f'{c.CYAN}{dir}{c.RESET}:{line_i + 1}') print(f'{c.MAGENTA}{rule["name"]}{c.RESET}: {rule["reason"]}') print(f'{prestr}{c.RED}{midstr}{c.RESET}{poststr}') print() if options['fix']: content_arr[line_i] = rule['fixerFn'](line, m) rule['found'] += 1 if options['fix']: file.write_text('\n'.join(content_arr)) def main(): rules: List[Rule] = [ { '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"', 'echo -n "Hello\\\\n"' ], 'testNegativeMatches': [ 'printf "%s\\n" "Hai"', 'echo -n "Hello\\n"' ], }, { '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)' ], 'testNegativeMatches': [ '$PWD' ], }, { '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 ]', '[ "${lines[0]}" == blah ]', ], 'testNegativeMatches': [ '[ a = b ]', '[[ a = b ]]', '[[ a == b ]]', '[ a = b ] || [[ a == b ]]', '[[ a = b ]] || [[ a == b ]]', '[[ "${lines[0]}" == \'usage: \'* ]]', '[ "${lines[0]}" = blah ]', ], }, { '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() { :; }', 'function fn { :; }', ], 'testNegativeMatches': [ 'fn() { :; }', ], }, { '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: 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: 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}') print() print('Done.') return options = { 'fix': args.fix, 'verbose': args.verbose, } # parse files and print matched lints if len(args.files) > 0: for file in args.files: p = Path(file) if p.is_file(): lintfile(p, rules, options) else: for file in Path.cwd().glob('**/*'): 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}') for rule in rules: print(f'{c.MAGENTA}{rule["name"]}{c.RESET}: {rule["found"]}') grand_total = sum([rule['found'] for rule in rules]) print(f'GRAND TOTAL: {grand_total}') # exit if grand_total == 0: exit(0) else: exit(2) main() ================================================ FILE: scripts/install_dependencies.bash ================================================ #!/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 variables for tracking versions # Elvish elvish_semver="v0.19.2" # Nushell nushell_semver="0.86.0" # Powershell powershell_semver="7.4.6" 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 --allow-downgrades -y install curl parallel \ fish 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" bats_version=$(grep -Eo "^\\s*bats\\s*.*$" ".tool-versions" | cut -d ' ' -f2-) git clone --depth 1 --branch "v$bats_version" https://github.com/bats-core/bats-core.git "$HOME/bats-core" echo "$HOME/bats-core/bin" >>"$GITHUB_PATH" ================================================ FILE: scripts/lint.bash ================================================ #!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' print.info() { printf '[INFO] %s\n' "$*" } print.error() { printf '[ERROR] %s\n' "$*" >&2 } 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 print.info "Checking .bash with shfmt" shfmt --language-dialect bash --indent 2 "${shfmt_flag}" \ internal/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/* print.info "Checking .bats with shfmt" shfmt --language-dialect bats --indent 2 "${shfmt_flag}" \ test/*.bats } run_shellcheck_linter() { print.info "Checking .sh files with Shellcheck" shellcheck --shell sh --external-sources \ asdf.sh print.info "Checking .bash files with Shellcheck" shellcheck --shell bash --external-sources \ internal/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/* print.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 print.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 print.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 print.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 print.info "Checking .fish files with fish_indent" fish_indent "${flag}" ./internal/completions/asdf.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_dir=$(git rev-parse --show-toplevel) current_dir=$(pwd -P) if [ "$repo_dir" != "$current_dir" ]; then print.error "This scripts requires execution from the repository root directory." printf "\t%s\t%s\n" "Repo root dir:" "$repo_dir" printf "\t%s\t%s\n\n" "Current dir:" "$current_dir" exit 1 fi } if [ $# -eq 0 ]; then print.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" ;; *) print.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" print.info "Success!" ================================================ FILE: scripts/test.bash ================================================ #!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' print.info() { printf '[INFO] %s\n' "$1" } print.error() { printf '[ERROR] %s\n' "$1" >&2 } { repo_dir=$(git rev-parse --show-toplevel) current_dir=$(pwd -P) if [ "$repo_dir" != "$current_dir" ]; then print.error "This scripts requires execution from the repository root directory." printf "\t%s\t%s\n" "Repo root dir:" "$repo_dir" printf "\t%s\t%s\n\n" "Current dir:" "$current_dir" exit 1 fi } test_directory="./test" bats_options=(--timing --print-output-on-failure) if command -v parallel >/dev/null; then # Enable parallel jobs bats_options+=(--jobs 2 --no-parallelize-within-files) elif [[ -n "${CI-}" ]]; then print.error "GNU parallel should be installed in the CI environment. Please install and rerun the test suite." exit 1 else print.info "For faster test execution, install GNU parallel." fi print.info "Running Bats in directory '${test_directory}' with options:" "${bats_options[@]}" bats "${bats_options[@]}" "${test_directory}" ================================================ FILE: staticcheck.conf ================================================ checks = ["all", "-ST1005"] ================================================ FILE: test/asdf_elvish.bats ================================================ #!/usr/bin/env bats # shellcheck disable=SC2030,SC2031 load test_helpers setup() { export XDG_CONFIG_HOME= 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)) && [ -z "$GITHUB_ACTIONS" ]; then skip "Elvish version is not at least 0.18. Found ${ver_major}.${ver_minor}.${ver_patch}" fi } cleaned_path() { echo "$PATH" | tr ':' '\n' | grep -v "asdf" | tr '\n' ' ' } @test "exports ASDF_DIR" { run elvish -norc -c " unset-env ASDF_DIR set paths = [$(cleaned_path)] use ./asdf _asdf; var asdf~ = \$_asdf:asdf~ echo \$E:ASDF_DIR" [ "$status" -eq 0 ] [ "$output" = "$HOME/.asdf" ] } @test "retains ASDF_DIR" { run elvish -norc -c " set-env ASDF_DIR \"/path/to/asdf\" set paths = [$(cleaned_path)] use ./asdf _asdf; var asdf~ = \$_asdf:asdf~ echo \$E:ASDF_DIR" [ "$status" -eq 0 ] [ "$output" = "/path/to/asdf" ] } @test "retains ASDF_DATA_DIR" { run elvish -norc -c " set-env ASDF_DATA_DIR \"/path/to/asdf-data\" set paths = [$(cleaned_path)] use ./asdf _asdf; var asdf~ = \$_asdf:asdf~ echo \$E:ASDF_DATA_DIR" [ "$status" -eq 0 ] [ "$output" = "/path/to/asdf-data" ] } @test "adds asdf dirs to PATH" { run elvish -norc -c " unset-env ASDF_DIR set paths = [$(cleaned_path)] use ./asdf _asdf; var asdf~ = \$_asdf:asdf~ echo \$E:PATH" [ "$status" -eq 0 ] result=$(echo "$output" | grep "asdf") [ "$result" != "" ] } @test "defines the _asdf namespace" { run elvish -norc -c " unset-env ASDF_DIR set paths = [$(cleaned_path)] use ./asdf _asdf; var asdf~ = \$_asdf:asdf~ pprint \$_asdf:" [ "$status" -eq 0 ] [[ "$output" =~ "<ns " ]] } @test "does not add paths to PATH more than once" { run elvish -norc -c " unset-env ASDF_DIR set paths = [$(cleaned_path)] use ./asdf _asdf; var asdf~ = \$_asdf:asdf~ use ./asdf _asdf; var asdf~ = \$_asdf:asdf~ echo \$E:PATH" [ "$status" -eq 0 ] result=$(echo "$result" | tr ':' '\n' | grep "asdf" | sort | uniq -d) [ "$result" = "" ] } @test "defines the asdf function" { run elvish -norc -c " unset-env ASDF_DIR set paths = [$(cleaned_path)] use ./asdf _asdf; var asdf~ = \$_asdf:asdf~ pprint \$asdf~" [ "$status" -eq 0 ] if [ "$(uname)" = "Linux" ]; then [[ "$output" =~ "<closure " ]] else [[ "$output" =~ ^'[^fn ' ]] fi } @test "function calls asdf command" { run elvish -norc -c " set-env ASDF_DIR $(pwd) # checkstyle-ignore set paths = [$(cleaned_path)] use ./asdf _asdf; var asdf~ = \$_asdf:asdf~ asdf info" [ "$status" -eq 0 ] result=$(echo "$output" | grep "ASDF INSTALLED PLUGINS:") [ "$result" != "" ] } ================================================ FILE: test/asdf_fish.bats ================================================ #!/usr/bin/env bats # shellcheck disable=SC2164 load test_helpers setup() { cd "$(dirname "$BATS_TEST_DIRNAME")" if ! command -v fish &>/dev/null && [ -z "$GITHUB_ACTIONS" ]; then skip "Fish is not installed" fi } cleaned_path() { echo "$PATH" | tr ':' '\n' | grep -v "asdf" | tr '\n' ' ' } @test "exports ASDF_DIR" { run fish --no-config -c " set -e asdf set -e ASDF_DIR set -e ASDF_DATA_DIR set PATH $(cleaned_path) . asdf.fish echo \$ASDF_DIR" [ "$status" -eq 0 ] [ "$output" != "" ] } @test "adds asdf dirs to PATH" { run fish --no-config -c " set -e asdf set -e ASDF_DIR set -e ASDF_DATA_DIR set PATH $(cleaned_path) . (pwd)/asdf.fish # if the full path is not passed, status -f will return the relative path echo \$PATH" [ "$status" -eq 0 ] result=$(echo "$output" | grep "asdf") [ "$result" != "" ] } @test "does not add paths to PATH more than once" { run fish --no-config -c " set -e asdf set -e ASDF_DIR set -e ASDF_DATA_DIR set PATH $(cleaned_path) . asdf.fish . asdf.fish echo \$PATH" [ "$status" -eq 0 ] result=$(echo "$output" | tr ' ' '\n' | grep "asdf" | sort | uniq -d) [ "$result" = "" ] } @test "defines the asdf function" { run fish --no-config -c " set -e asdf set -e ASDF_DIR set PATH $(cleaned_path) . asdf.fish type asdf" [ "$status" -eq 0 ] [[ "$output" =~ "is a function" ]] } @test "function calls asdf command" { run fish --no-config -c " set -e asdf set -x ASDF_DIR $(pwd) # checkstyle-ignore set PATH $(cleaned_path) . asdf.fish asdf info" [ "$status" -eq 0 ] result=$(echo "$output" | grep "ASDF INSTALLED PLUGINS:") [ "$result" != "" ] } ================================================ FILE: test/asdf_nu.bats ================================================ #!/usr/bin/env bats # shellcheck disable=SC2164 load test_helpers setup() { cd "$(dirname "$BATS_TEST_DIRNAME")" 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' ':' } run_nushell() { run nu -c " hide-env -i asdf hide-env -i ASDF_DIR \$env.PATH = ( '$(cleaned_path)' | split row ':' ) \$env.ASDF_DIR = '$PWD' source asdf.nu $1" } @test "exports ASDF_DIR" { run_nushell "echo \$env.ASDF_DIR" [ "$status" -eq 0 ] result=$(echo "$output" | grep "asdf") [ "$result" = "$PWD" ] } @test "adds asdf dirs to PATH" { run_nushell "\$env.PATH | to text" [ "$status" -eq 0 ] [[ "$output" == *"$PWD/bin"* ]] [[ "$output" == *"$HOME/.asdf/shims"* ]] } @test "does not add paths to PATH more than once" { run_nushell " source asdf.nu echo \$env.PATH" [ "$status" -eq 0 ] result=$(echo "$output" | tr ' ' '\n' | grep "asdf" | sort | uniq -d) [ "$result" = "" ] } @test "retains ASDF_DIR (from ASDF_NU_DIR)" { run nu -c " hide-env -i asdf \$env.ASDF_DIR = ( pwd ) \$env.PATH = ( '$(cleaned_path)' | split row ':' ) \$env.ASDF_NU_DIR = '$PWD' source asdf.nu echo \$env.ASDF_DIR" [ "$status" -eq 0 ] [ "$output" = "$PWD" ] } @test "retains ASDF_DIR (from ASDF_DIR)" { run nu -c " hide-env -i asdf \$env.ASDF_DIR = ( pwd ) \$env.PATH = ( '$(cleaned_path)' | split row ':' ) \$env.ASDF_DIR = '$PWD' source asdf.nu echo \$env.ASDF_DIR" [ "$status" -eq 0 ] [ "$output" = "$PWD" ] } @test "defines the asdf or main function" { run_nushell "which asdf | get path | to text" [ "$status" -eq 0 ] } @test "function calls asdf command" { 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" ] } # TODO: Fix as soon as possible https://github.com/asdf-vm/asdf/issues/1808 #@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" ] } ================================================ FILE: test/asdf_pwsh.bats ================================================ #!/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" != "" ] } ================================================ FILE: test/asdf_sh.bats ================================================ #!/usr/bin/env bats load test_helpers # Helper function to handle sourcing of asdf.sh source_asdf_sh() { . "$(dirname "$BATS_TEST_DIRNAME")/asdf.sh" } cleaned_path() { echo "$PATH" | tr ':' '\n' | grep -v "asdf" | tr '\n' ':' } @test "exports ASDF_DIR" { output=$( unset -f asdf unset ASDF_DIR PATH=$(cleaned_path) source_asdf_sh echo "$ASDF_DIR" ) result=$(echo "$output" | grep "asdf") [ "$result" != "" ] } @test "does not error if nounset is enabled" { output=$( unset -f asdf unset ASDF_DIR PATH=$(cleaned_path) set -o nounset source_asdf_sh echo "$ASDF_DIR" ) result=$(echo "$output" | grep "asdf") [ "$result" != "" ] } @test "adds asdf dirs to PATH" { output=$( unset -f asdf unset ASDF_DIR PATH=$(cleaned_path) source_asdf_sh echo "$PATH" ) result=$(echo "$output" | grep "asdf") [ "$result" != "" ] } @test "does not add paths to PATH more than once" { output=$( unset -f asdf unset ASDF_DIR PATH=$(cleaned_path) source_asdf_sh source_asdf_sh echo "$PATH" ) result=$(echo "$output" | tr ':' '\n' | grep "asdf" | sort | uniq -d) [ "$result" = "" ] } @test "defines the asdf function" { output=$( unset -f asdf unset ASDF_DIR PATH=$(cleaned_path) source_asdf_sh type asdf ) [[ "$output" =~ "is a function" ]] } @test "function calls asdf command" { result=$( unset -f asdf ASDF_DIR=$PWD PATH=$(cleaned_path) source_asdf_sh asdf info ) output=$(echo "$result" | grep "ASDF INSTALLED PLUGINS:") [ "$output" != "" ] } ================================================ FILE: test/banned_commands.bats ================================================ #!/usr/bin/env bats load test_helpers banned_commands=( # Process substitution isn't POSIX compliant and cause trouble "<(" # Command isn't included in the Ubuntu packages asdf depends on. Also not # defined in POSIX column # echo isn't consistent across operating systems, and sometimes output can # be confused with echo flags. printf does everything echo does and more. echo # It's best to avoid eval as it makes it easier to accidentally execute # arbitrary strings eval # realpath not available by default on OSX. realpath # 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 # For consistency, [ should be used instead. There is a leading space so 'fail_test', etc. is not matched ' test' ) banned_commands_regex=( # grep -y does not work on alpine and should be "grep -i" either way "grep.* -y" # grep -P is not a valid option in OSX. "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" # ls often gets used when we want to glob for files that match a pattern # or when we want to find all files/directories that match a pattern or are # found in a certain location. Using shell globs is preferred over ls, and # find is better at locating files that are in a certain location or that # match certain filename patterns. # https://github-wiki-see.page/m/koalaman/shellcheck/wiki/SC2012 '\bls ' # Ban recursive asdf calls as they are inefficient and may introduce bugs. # If you find yourself needing to invoke an `asdf` command from within # asdf code, please source the appropriate file and invoke the # corresponding function. '\basdf ' ) setup() { setup_asdf_dir } teardown() { clean_asdf_dir } @test "banned commands are not found in source code" { # Assert command is not used in the lib and bin dirs # or expect an explicit comment at end of line, allowing it. # Also ignore matches that are contained in comments or a string or # followed by an underscore (indicating it's a variable and not a # command). for cmd in "${banned_commands[@]}"; do run bash -c "grep -nHR --include \*.bash --include \*.sh '$cmd' asdf.* lib bin\ | grep -v '#.*$cmd'\ | grep -v '\".*$cmd.*\"' \ | grep -v '${cmd}_'\ | grep -v '# asdf_allow: $cmd'" # Only print output if we've found a banned command #if [ "$status" -ne 1 ]; then if [ "" != "$output" ]; then echo "banned command $cmd: $output" fi [ "$status" -eq 1 ] [ "" = "$output" ] done for cmd in "${banned_commands_regex[@]}"; do run bash -c "grep -nHRE --include \*.bash --include \*.sh '$cmd' asdf.* lib bin\ | grep -v '#.*$cmd'\ | grep -v '\".*$cmd.*\"' \ | grep -v '${cmd}_'\ | grep -v '# asdf_allow: $cmd'" # Only print output if we've found a banned command #if [ "$status" -ne 1 ]; then if [ "" != "$output" ]; then echo "banned command $cmd: $output" fi [ "$status" -eq 1 ] [ "" = "$output" ] done } ================================================ FILE: test/current_command.bats ================================================ #!/usr/bin/env bats load test_helpers setup() { setup_asdf_dir install_dummy_plugin install_dummy_version "1.1.0" install_dummy_version "1.2.0" install_dummy_version "nightly-2000-01-01" PROJECT_DIR="$HOME/project" mkdir -p "$PROJECT_DIR" } teardown() { clean_asdf_dir } @test "current should derive from the current .tool-versions" { cd "$PROJECT_DIR" echo 'dummy 1.1.0' >>"$PROJECT_DIR/.tool-versions" expected="Name Version Source Installed dummy 1.1.0 $PROJECT_DIR/.tool-versions true" run asdf current "dummy" # shellcheck disable=SC2001 condensed_output="$(sed -e 's/ [ ]*/ /g' <<<"$output")" [ "$status" -eq 0 ] [ "$condensed_output" = "$expected" ] } @test "current should handle long version name" { cd "$PROJECT_DIR" echo "dummy nightly-2000-01-01" >>"$PROJECT_DIR/.tool-versions" expected="Name Version Source Installed dummy nightly-2000-01-01 $PROJECT_DIR/.tool-versions true" run asdf current "dummy" # shellcheck disable=SC2001 condensed_output="$(sed -e 's/ [ ]*/ /g' <<<"$output")" [ "$status" -eq 0 ] [ "$condensed_output" = "$expected" ] } @test "current should handle multiple versions" { cd "$PROJECT_DIR" echo "dummy 1.2.0 1.1.0" >>"$PROJECT_DIR/.tool-versions" expected="Name Version Source Installed dummy 1.2.0 1.1.0 $PROJECT_DIR/.tool-versions true" run asdf current "dummy" # shellcheck disable=SC2001 condensed_output="$(sed -e 's/ [ ]*/ /g' <<<"$output")" [ "$status" -eq 0 ] [ "$condensed_output" = "$expected" ] } @test "current should derive from the legacy file if enabled" { cd "$PROJECT_DIR" echo 'legacy_version_file = yes' >"$HOME/.asdfrc" echo '1.2.0' >>"$PROJECT_DIR/.dummy-version" expected="Name Version Source Installed dummy 1.2.0 $PROJECT_DIR/.dummy-version true" run asdf current "dummy" # shellcheck disable=SC2001 condensed_output="$(sed -e 's/ [ ]*/ /g' <<<"$output")" [ "$status" -eq 0 ] [ "$condensed_output" = "$expected" ] } # TODO: Need to fix plugin error as well @test "current should error when the plugin doesn't exist" { expected="No such plugin: foobar" run asdf current "foobar" [ "$status" -eq 1 ] [ "$output" = "$expected" ] } @test "current should error when no version is set" { cd "$PROJECT_DIR" expected="Name Version Source Installed dummy ______ ______ " run asdf current "dummy" # shellcheck disable=SC2001 condensed_output="$(sed -e 's/ [ ]*/ /g' <<<"$output")" [ "$status" -eq 126 ] [ "$condensed_output" = "$expected" ] } @test "current should error when a version is set that isn't installed" { cd "$PROJECT_DIR" echo 'dummy 9.9.9' >>"$PROJECT_DIR/.tool-versions" expected="Name Version Source Installed dummy 9.9.9 $PROJECT_DIR/.tool-versions false - Run \`asdf install dummy 9.9.9\`" asdf uninstall dummy 9.9.9 || true run asdf current "dummy" # shellcheck disable=SC2001 condensed_output="$(sed -e 's/ [ ]*/ /g' -e 's/ $//g' <<<"$output")" [ "$status" -eq 1 ] [ "$condensed_output" = "$expected" ] } @test "should output all plugins when no plugin passed" { #install_dummy_plugin install_dummy_version "1.1.0" install_mock_plugin "foobar" install_mock_plugin_version "foobar" "1.0.0" install_mock_plugin "baz" cd "$PROJECT_DIR" echo 'dummy 1.1.0' >>"$PROJECT_DIR/.tool-versions" echo 'foobar 1.0.0' >>"$PROJECT_DIR/.tool-versions" expected="Name Version Source Installed baz ______ ______ dummy 1.1.0 $PROJECT_DIR/.tool-versions true foobar 1.0.0 $PROJECT_DIR/.tool-versions true" run asdf current # shellcheck disable=SC2001 condensed_output="$(sed -e 's/ [ ]*/ /g' -e 's/ $//g' <<<"$output")" [ "$expected" = "$condensed_output" ] } @test "should always match the tool name exactly" { #install_dummy_plugin install_dummy_version "1.1.0" install_mock_plugin "y" install_mock_plugin_version "y" "2.1.0" cd "$PROJECT_DIR" echo 'dummy 1.1.0' >>"$PROJECT_DIR/.tool-versions" echo 'y 2.1.0' >>"$PROJECT_DIR/.tool-versions" run asdf current "y" [ "$status" -eq 0 ] [[ "$output" == *'2.1.0'* ]] } @test "with no plugins prints an error" { clean_asdf_dir expected="No plugins installed" run asdf current [ "$status" -eq 0 ] [ "$output" = "$expected" ] } @test "current should handle comments" { cd "$PROJECT_DIR" echo "dummy 1.2.0 # this is a comment" >>"$PROJECT_DIR/.tool-versions" expected="Name Version Source Installed dummy 1.2.0 $PROJECT_DIR/.tool-versions true" run asdf current "dummy" [ "$status" -eq 0 ] # shellcheck disable=SC2001 condensed_output="$(sed -e 's/ [ ]*/ /g' <<<"$output")" [ "$condensed_output" = "$expected" ] } ================================================ FILE: test/fixtures/dummy_broken_plugin/bin/download ================================================ #!/usr/bin/env bash echo "Download failed!" exit 1 ================================================ FILE: test/fixtures/dummy_broken_plugin/bin/install ================================================ #!/usr/bin/env bash echo "Unused script" ================================================ FILE: test/fixtures/dummy_broken_plugin/bin/list-all ================================================ #!/usr/bin/env bash echo "Attempting to list versions" echo "List-all failed!" >&2 exit 1 ================================================ FILE: test/fixtures/dummy_legacy_plugin/bin/get-version-from-legacy-file ================================================ #!/usr/bin/env bash get_legacy_version() { current_directory=$1 version_file="$current_directory/.dummy-version" if [ -f "$version_file" ]; then cat "$version_file" fi } get_legacy_version "$1" ================================================ FILE: test/fixtures/dummy_legacy_plugin/bin/install ================================================ #!/usr/bin/env bash mkdir -p "$ASDF_INSTALL_PATH" env >"$ASDF_INSTALL_PATH/env" echo "$ASDF_INSTALL_VERSION" >"$ASDF_INSTALL_PATH/version" # create the dummy executable mkdir -p "$ASDF_INSTALL_PATH/bin" cat <<EOF >"$ASDF_INSTALL_PATH/bin/dummy" echo This is Dummy ${ASDF_INSTALL_VERSION}! \$2 \$1 EOF chmod +x "$ASDF_INSTALL_PATH/bin/dummy" mkdir -p "$ASDF_INSTALL_PATH/bin/subdir" cat <<EOF >"$ASDF_INSTALL_PATH/bin/subdir/other_bin" echo This is Other Bin ${ASDF_INSTALL_VERSION}! \$2 \$1 EOF chmod +x "$ASDF_INSTALL_PATH/bin/subdir/other_bin" ================================================ FILE: test/fixtures/dummy_legacy_plugin/bin/list-all ================================================ #!/usr/bin/env bash versions_list=(1.0.0 1.1.0 2.0.0 3.0.0-alpha1 3.0.0-beta2 4.0.0 4.1.0-pre 5.0.0-Alpha1 5.1.0 5.2.0-Alpha2) echo "${versions_list[@]}" ================================================ FILE: test/fixtures/dummy_legacy_plugin/bin/list-legacy-filenames ================================================ #!/usr/bin/env bash echo ".dummy-version .dummyrc" ================================================ FILE: test/fixtures/dummy_legacy_plugin/bin/parse-legacy-file ================================================ #!/usr/bin/env bash # shellcheck disable=SC2020 tr <"$1" -d "dummy-" ================================================ FILE: test/fixtures/dummy_plugin/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2014 Akash Manohar J Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: test/fixtures/dummy_plugin/bin/debug ================================================ #!/usr/bin/env bash echo "$@" ================================================ FILE: test/fixtures/dummy_plugin/bin/download ================================================ #!/usr/bin/env bash exit 0 ================================================ FILE: test/fixtures/dummy_plugin/bin/get-version-from-legacy-file ================================================ #!/usr/bin/env bash get_legacy_version() { current_directory=$1 version_file="$current_directory/.dummy-version" if [ -f "$version_file" ]; then cat "$version_file" fi } get_legacy_version "$1" ================================================ FILE: test/fixtures/dummy_plugin/bin/help.overview ================================================ #!/usr/bin/env bash echo "Dummy plugin documentation" echo echo "Dummy plugin is a plugin only used for unit tests" if [ -n "$ASDF_INSTALL_VERSION" ]; then echo echo "Details specific for version $ASDF_INSTALL_VERSION" fi ================================================ FILE: test/fixtures/dummy_plugin/bin/install ================================================ #!/usr/bin/env bash # We want certain versions to fail installation for various reasons in the tests check_dummy_versions() { local bad_versions=" other-dummy " if [[ "$bad_versions" == *" $ASDF_INSTALL_VERSION "* ]]; then echo "Dummy couldn't install version: $ASDF_INSTALL_VERSION (on purpose)" exit 1 fi } check_dummy_versions mkdir -p "$ASDF_INSTALL_PATH" env >"$ASDF_INSTALL_PATH/env" echo "$ASDF_INSTALL_VERSION" >"$ASDF_INSTALL_PATH/version" # create the dummy executable mkdir -p "$ASDF_INSTALL_PATH/bin" cat <<EOF >"$ASDF_INSTALL_PATH/bin/dummy" #!/usr/bin/env bash echo This is Dummy ${ASDF_INSTALL_VERSION}! \$2 \$1 EOF chmod +x "$ASDF_INSTALL_PATH/bin/dummy" mkdir -p "$ASDF_INSTALL_PATH/bin/subdir" cat <<EOF >"$ASDF_INSTALL_PATH/bin/subdir/other_bin" #!/usr/bin/env bash echo This is Other Bin ${ASDF_INSTALL_VERSION}! \$2 \$1 EOF chmod +x "$ASDF_INSTALL_PATH/bin/subdir/other_bin" ================================================ FILE: test/fixtures/dummy_plugin/bin/latest-stable ================================================ #!/usr/bin/env bash get_latest_stable() { query=$1 version_list=(1.0.0 1.1.0 2.0.0) printf "%s\n" "${version_list[@]}" | grep -E "^\s*$query" | tail -1 } get_latest_stable "$1" ================================================ FILE: test/fixtures/dummy_plugin/bin/list-all ================================================ #!/usr/bin/env bash versions_list=(1.0.0 1.1.0 2.0.0) echo "${versions_list[@]}" # Sending message to STD error to ensure that it is ignored echo "ignore this error" >&2 ================================================ FILE: test/fixtures/dummy_plugin/bin/list-legacy-filenames ================================================ #!/usr/bin/env bash echo ".dummy-version .dummyrc" ================================================ FILE: test/fixtures/dummy_plugin/bin/parse-legacy-file ================================================ #!/usr/bin/env bash # shellcheck disable=SC2020 tr <"$1" -d "dummy-" ================================================ FILE: test/fixtures/dummy_plugin/bin/post-plugin-add ================================================ #!/usr/bin/env bash echo "plugin add path=${ASDF_PLUGIN_PATH} source_url=${ASDF_PLUGIN_SOURCE_URL}" ================================================ FILE: test/fixtures/dummy_plugin/bin/post-plugin-update ================================================ #!/usr/bin/env bash echo "plugin updated path=${ASDF_PLUGIN_PATH} old git-ref=${ASDF_PLUGIN_PREV_REF} new git-ref=${ASDF_PLUGIN_POST_REF}" ================================================ FILE: test/fixtures/dummy_plugin/bin/pre-plugin-remove ================================================ #!/usr/bin/env bash echo "plugin-remove ${ASDF_PLUGIN_PATH}" ================================================ FILE: test/fixtures/dummy_plugin_no_download/LICENSE ================================================ The MIT License (MIT) Copyright (c) 2014 Akash Manohar J Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: test/fixtures/dummy_plugin_no_download/bin/install ================================================ #!/usr/bin/env bash printf '%s' 'install' ================================================ FILE: test/fixtures/dummy_plugins_repo/plugins/bar ================================================ repository = http://example.com/bar ================================================ FILE: test/fixtures/dummy_plugins_repo/plugins/dummy ================================================ repository = http://example.com/dummy ================================================ FILE: test/fixtures/dummy_plugins_repo/plugins/foo ================================================ repository = http://example.com/foo ================================================ FILE: test/get_asdf_config_value.bats ================================================ #!/usr/bin/env bats # shellcheck disable=SC2164 load test_helpers setup() { cd "$BATS_TMPDIR" ASDF_CONFIG_FILE="$BATS_TMPDIR/asdfrc" cat >"$ASDF_CONFIG_FILE" <<-EOM key1 = value1 legacy_version_file = yes EOM ASDF_CONFIG_DEFAULT_FILE="$BATS_TMPDIR/asdfrc_defaults" cat >"$ASDF_CONFIG_DEFAULT_FILE" <<-EOM # i have a comment, it's ok key2 = value2 legacy_version_file = no EOM } teardown() { rm "$ASDF_CONFIG_FILE" rm "$ASDF_CONFIG_DEFAULT_FILE" unset ASDF_CONFIG_DEFAULT_FILE unset ASDF_CONFIG_FILE } @test "get_config returns default when config file does not exist" { result=$(ASDF_CONFIG_FILE="/some/fake/path" get_asdf_config_value "legacy_version_file") [ "$result" = "no" ] } @test "get_config returns default value when the key does not exist" { [ "$(get_asdf_config_value "key2")" = "value2" ] } @test "get_config returns config file value when key exists" { [ "$(get_asdf_config_value "key1")" = "value1" ] [ "$(get_asdf_config_value "legacy_version_file")" = "yes" ] } @test "get_config returns config file complete value including '=' symbols" { cat >>"$ASDF_CONFIG_FILE" <<-'EOM' key3 = VAR=val EOM [ "$(get_asdf_config_value "key3")" = "VAR=val" ] } ================================================ FILE: test/help_command.bats ================================================ #!/usr/bin/env bats load test_helpers setup() { setup_asdf_dir install_dummy_plugin install_dummy_legacy_plugin run asdf install dummy 1.0 run asdf install dummy 1.1 PROJECT_DIR="$HOME/project" mkdir -p "$PROJECT_DIR" } teardown() { clean_asdf_dir } @test "help should show dummy plugin help" { cd "$PROJECT_DIR" run asdf help "dummy" expected_output="$( cat <<EOF Dummy plugin documentation Dummy plugin is a plugin only used for unit tests EOF )" [ "$status" -eq 0 ] [ "$output" = "$expected_output" ] } @test "help should show dummy plugin help specific to version when version is present" { cd "$PROJECT_DIR" run asdf help "dummy" "1.2.3" expected_output="$( cat <<EOF Dummy plugin documentation Dummy plugin is a plugin only used for unit tests Details specific for version 1.2.3 EOF )" [ "$status" -eq 0 ] [ "$output" = "$expected_output" ] } @test "help should fail for unknown plugins" { cd "$PROJECT_DIR" run asdf help "sunny" [ "$status" -eq 1 ] [ "$output" = "No plugin named sunny" ] } @test "help should fail when plugin doesn't have documentation callback" { cd "$PROJECT_DIR" run asdf help "legacy-dummy" [ "$status" -eq 1 ] [ "$output" = "No documentation for plugin legacy-dummy" ] } @test "help should show asdf help when no plugin name is provided" { cd "$PROJECT_DIR" run asdf help [ "$status" -eq 0 ] [[ $output =~ $'version: '[0-9]* ]] [[ $output == *$'MANAGE PLUGINS\n'* ]] [[ $output == *$'MANAGE TOOLS\n'* ]] [[ $output == *$'UTILS\n'* ]] [[ $output == *$'"Late but latest"\n-- Rajinikanth' ]] } ================================================ FILE: test/info_command.bats ================================================ #!/usr/bin/env bats load test_helpers setup() { setup_asdf_dir install_dummy_plugin install_dummy_legacy_plugin run asdf install dummy 1.0 run asdf install dummy 1.1 PROJECT_DIR="$HOME/project" mkdir -p "$PROJECT_DIR" } teardown() { clean_asdf_dir } @test "info should show os, shell and asdf debug information" { cd "$PROJECT_DIR" run asdf info [ "$status" -eq 0 ] [[ $output == *$'OS:\n'* ]] [[ $output == *$'SHELL:\n'* ]] [[ $output == *$'BASH VERSION:\n'* ]] [[ $output == *$'ASDF VERSION:\n'* ]] [[ $output == *$'ASDF INTERNAL VARIABLES:\n'* ]] [[ $output == *$'ASDF INSTALLED PLUGINS:\n'* ]] } ================================================ FILE: test/install_command.bats ================================================ #!/usr/bin/env bats load test_helpers setup() { setup_asdf_dir install_dummy_legacy_plugin install_dummy_plugin install_dummy_broken_plugin install_dummy_plugin_no_download PROJECT_DIR="$HOME/project" mkdir -p "$PROJECT_DIR" } teardown() { clean_asdf_dir } @test "install_command installs the correct version" { run asdf install dummy 1.1.0 [ "$status" -eq 0 ] [ "$(cat "$ASDF_DIR/installs/dummy/1.1.0/version")" = "1.1.0" ] } @test "install_command installs the correct version for plugins without download script" { run asdf install legacy-dummy 1.1.0 [ "$status" -eq 0 ] [ "$(cat "$ASDF_DIR/installs/legacy-dummy/1.1.0/version")" = "1.1.0" ] } @test "install_command without arguments installs even if the user is terrible and does not use newlines" { cd "$PROJECT_DIR" echo -n 'dummy 1.2.0' >".tool-versions" run asdf install [ "$status" -eq 0 ] [ "$(cat "$ASDF_DIR/installs/dummy/1.2.0/version")" = "1.2.0" ] } @test "install_command with only name installs the version in .tool-versions" { cd "$PROJECT_DIR" echo -n 'dummy 1.2.0' >".tool-versions" run asdf install dummy [ "$status" -eq 0 ] [ "$(cat "$ASDF_DIR/installs/dummy/1.2.0/version")" = "1.2.0" ] } @test "install_command set ASDF_CONCURRENCY" { run asdf install dummy 1.0.0 [ "$status" -eq 0 ] [ -f "$ASDF_DIR/installs/dummy/1.0.0/env" ] run grep ASDF_CONCURRENCY "$ASDF_DIR/installs/dummy/1.0.0/env" [ "$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" cd "$WHITESPACE_DIR" echo 'dummy 1.2.0' >>"$WHITESPACE_DIR/.tool-versions" run asdf install [ "$status" -eq 0 ] [ "$(cat "$ASDF_DIR/installs/dummy/1.2.0/version")" = "1.2.0" ] } @test "install_command should create a shim with asdf-plugin metadata" { run asdf install dummy 1.0.0 [ "$status" -eq 0 ] [ -f "$ASDF_DIR/installs/dummy/1.0.0/env" ] run grep "asdf-plugin: dummy 1.0.0" "$ASDF_DIR/shims/dummy" [ "$status" -eq 0 ] } @test "install_command should create a shim with asdf-plugin metadata for plugins without download script" { run asdf install legacy-dummy 1.0.0 [ "$status" -eq 0 ] [ -f "$ASDF_DIR/installs/legacy-dummy/1.0.0/env" ] run grep "asdf-plugin: legacy-dummy 1.0.0" "$ASDF_DIR/shims/dummy" [ "$status" -eq 0 ] } @test "install_command on two versions should create a shim with asdf-plugin metadata" { run asdf install dummy 1.1.0 [ "$status" -eq 0 ] run grep "asdf-plugin: dummy 1.1.0" "$ASDF_DIR/shims/dummy" [ "$status" -eq 0 ] run grep "asdf-plugin: dummy 1.0.0" "$ASDF_DIR/shims/dummy" [ "$status" -eq 1 ] run asdf install dummy 1.0.0 [ "$status" -eq 0 ] run grep "asdf-plugin: dummy 1.0.0" "$ASDF_DIR/shims/dummy" [ "$status" -eq 0 ] run grep "# asdf-plugin: dummy 1.0.0"$'\n'"# asdf-plugin: dummy 1.1.0" "$ASDF_DIR/shims/dummy" [ "$status" -eq 0 ] lines_count=$(grep -c "asdf-plugin: dummy 1.1.0" "$ASDF_DIR/shims/dummy") [ "$lines_count" -eq "1" ] } @test "install_command without arguments should not generate shim for subdir" { cd "$PROJECT_DIR" echo 'dummy 1.0.0' >"$PROJECT_DIR/.tool-versions" run asdf install [ "$status" -eq 0 ] [ -f "$ASDF_DIR/shims/dummy" ] [ ! -f "$ASDF_DIR/shims/subdir" ] } @test "install_command without arguments should generate shim that passes all arguments to executable" { # asdf lib needed to run generated shims cp -rf "$BATS_TEST_DIRNAME"/../{bin,lib} "$ASDF_DIR/" cd "$PROJECT_DIR" echo 'dummy 1.0.0' >"$PROJECT_DIR/.tool-versions" run asdf install # execute the generated shim run "$ASDF_DIR/shims/dummy" world hello [ "$status" -eq 0 ] [ "$output" = "This is Dummy 1.0.0! hello world" ] } @test "install_command fails when tool is specified but no version of the tool is configured" { run asdf install dummy [ "$status" -eq 1 ] [ "$output" = "No versions specified for dummy in config files or environment" ] [ ! -f "$ASDF_DIR/installs/dummy/1.1.0/version" ] } # `asdf install` now enumerates installed plugins, so if a plugin defined in a # .tool-versions file is not installed `asdf install` now skips it. #@test "install_command fails if the plugin is not installed" { # cd "$PROJECT_DIR" # echo 'other_dummy 1.0.0' >"$PROJECT_DIR/.tool-versions" # run asdf install # [ "$status" -eq 1 ] # [ "$output" = "other_dummy plugin is not installed" ] #} # Not clear how this test differs from those above #@test "install_command fails if the plugin is not installed without collisions" { # cd "$PROJECT_DIR" # printf "dummy 1.0.0\ndum 1.0.0" >"$PROJECT_DIR/.tool-versions" # run asdf install # [ "$status" -eq 1 ] # [ "$output" = "dum plugin is not installed" ] #} @test "install_command fails when tool is specified but no version of the tool is configured in config file" { run asdf install dummy [ "$status" -eq 1 ] [ "$output" = "No versions specified for dummy in config files or environment" ] [ ! -f "$ASDF_DIR/installs/dummy/1.0.0/version" ] } @test "install_command fails when two tools are specified with no versions" { printf 'dummy 1.0.0\nother-dummy 2.0.0' >"$PROJECT_DIR/.tool-versions" run asdf install dummy other-dummy [ "$status" -eq 1 ] [ "$(head -n1 <<<"$output")" = "Dummy couldn't install version: other-dummy (on purpose)" ] [ ! -f "$ASDF_DIR/installs/dummy/1.0.0/version" ] [ ! -f "$ASDF_DIR/installs/other-dummy/2.0.0/version" ] } @test "install_command without arguments uses a parent directory .tool-versions file if present" { # asdf lib needed to run generated shims cp -rf "$BATS_TEST_DIRNAME"/../{bin,lib} "$ASDF_DIR/" echo 'dummy 1.0.0' >"$PROJECT_DIR/.tool-versions" mkdir -p "$PROJECT_DIR/child" cd "$PROJECT_DIR/child" run asdf install # execute the generated shim [ "$("$ASDF_DIR/shims/dummy" world hello)" = "This is Dummy 1.0.0! hello world" ] [ "$status" -eq 0 ] } @test "install_command installs multiple tool versions when they are specified in a .tool-versions file" { echo 'dummy 1.0.0 1.2.0' >"$PROJECT_DIR/.tool-versions" cd "$PROJECT_DIR" run asdf install [ "$status" -eq 0 ] [ "$(cat "$ASDF_DIR/installs/dummy/1.0.0/version")" = "1.0.0" ] [ "$(cat "$ASDF_DIR/installs/dummy/1.2.0/version")" = "1.2.0" ] } @test "install_command doesn't install system version" { run asdf install dummy system [ "$status" -eq 1 ] [ "$output" = "error installing version: uninstallable version system of dummy" ] [ ! -f "$ASDF_DIR/installs/dummy/system/version" ] } @test "install command executes configured pre plugin install hook" { cat >"$HOME/.asdfrc" <<-'EOM' pre_asdf_install_dummy = echo will install dummy $1 EOM run asdf install dummy 1.0.0 [ "$output" = "will install dummy 1.0.0" ] } # This test has been changed because variables like $version and $plugin_name # only worked because asdf was a Bash script and leaked those variables. @test "install command executes configured post plugin install hook" { cat >"$HOME/.asdfrc" <<-'EOM' post_asdf_install_dummy = echo HEY $1 FROM dummy EOM run asdf install dummy 1.0.0 [ "$output" = "HEY 1.0.0 FROM dummy" ] } @test "install command without arguments installs versions from legacy files" { echo 'legacy_version_file = yes' >"$HOME/.asdfrc" echo '1.2.0' >>"$PROJECT_DIR/.dummy-version" cd "$PROJECT_DIR" run asdf install [ "$status" -eq 0 ] [ -f "$ASDF_DIR/installs/dummy/1.2.0/version" ] } @test "install command without arguments installs versions from legacy files in parent directories" { echo 'legacy_version_file = yes' >"$HOME/.asdfrc" echo '1.2.0' >>"$PROJECT_DIR/.dummy-version" mkdir -p "$PROJECT_DIR/child" cd "$PROJECT_DIR/child" run asdf install [ "$status" -eq 0 ] [ -f "$ASDF_DIR/installs/dummy/1.2.0/version" ] } @test "install_command latest installs latest stable version" { run asdf install dummy latest [ "$status" -eq 0 ] [ "$(cat "$ASDF_DIR/installs/dummy/2.0.0/version")" = "2.0.0" ] } @test "install_command latest:version installs latest stable version that matches the given string" { run asdf install dummy latest:1 [ "$status" -eq 0 ] [ "$(cat "$ASDF_DIR/installs/dummy/1.1.0/version")" = "1.1.0" ] } @test "install_command deletes the download directory" { run asdf install dummy 1.1.0 [ "$status" -eq 0 ] [ ! -d "$ASDF_DIR/downloads/dummy/1.1.0" ] [ "$(cat "$ASDF_DIR/installs/dummy/1.1.0/version")" = "1.1.0" ] } @test "install_command keeps the download directory when --keep-download flag is provided" { # Original code: # run asdf install dummy 1.1.0 --keep-download # Flags should be allowed anywhere, but unfortunately the CLI arg parser # I'm using only allows them before positional arguments. Hence I've had to # update this test. But we should fix this soon. run asdf install --keep-download dummy 1.1.0 [ "$status" -eq 0 ] [ -d "$ASDF_DIR/downloads/dummy/1.1.0" ] [ "$(cat "$ASDF_DIR/installs/dummy/1.1.0/version")" = "1.1.0" ] } @test "install_command keeps the download directory when always_keep_download setting is true" { echo 'always_keep_download = yes' >"$HOME/.asdfrc" run asdf install dummy 1.1.0 [ "$status" -eq 0 ] [ -d "$ASDF_DIR/downloads/dummy/1.1.0" ] [ "$(cat "$ASDF_DIR/installs/dummy/1.1.0/version")" = "1.1.0" ] } @test "install_command fails when download script exits with non-zero code" { run asdf install dummy-broken 1.0.0 [ "$status" -eq 1 ] [ ! -d "$ASDF_DIR/downloads/dummy-broken/1.1.0" ] [ ! -d "$ASDF_DIR/installs/dummy-broken/1.1.0" ] [ "$(head -n1 <<<"$output")" = "Download failed!" ] } # Download callback is now required #@test "install_command prints info message if plugin does not support preserving download data if configured" { # install_dummy_plugin_no_download # # run asdf install dummy-no-download 1.0.0 # [ "$status" -eq 0 ] # [[ "$output" == *'asdf: Warn:'*'not be preserved'* ]] #} @test "install command without arguments installs version a second time without errors" { cd "$PROJECT_DIR" echo -n 'dummy 1.2.0' >".tool-versions" run asdf install [ "$status" -eq 0 ] [ "$(cat "$ASDF_DIR/installs/dummy/1.2.0/version")" = "1.2.0" ] run asdf install [ "$status" -eq 0 ] } @test "install command with tool installs version a second time without errors" { cd "$PROJECT_DIR" echo -n 'dummy 1.2.0' >".tool-versions" run asdf install dummy [ "$status" -eq 0 ] [ "$(cat "$ASDF_DIR/installs/dummy/1.2.0/version")" = "1.2.0" ] run asdf install dummy [ "$status" -eq 0 ] } @test "install command with tool and version installs version a second time without errors" { run asdf install dummy 1.0.0 [ "$status" -eq 0 ] [ "$(cat "$ASDF_DIR/installs/dummy/1.0.0/version")" = "1.0.0" ] run asdf install dummy 1.0.0 [ "$status" -eq 0 ] } @test "install command with tool and different version installs version a second time without errors" { cd "$PROJECT_DIR" echo -n 'dummy 1.0.0 1.1.0' >".tool-versions" run asdf install dummy 1.0.0 [ "$status" -eq 0 ] [ "$(cat "$ASDF_DIR/installs/dummy/1.0.0/version")" = "1.0.0" ] run asdf install [ "$status" -eq 0 ] [ "$(cat "$ASDF_DIR/installs/dummy/1.1.0/version")" = "1.1.0" ] } @test "install command with two tools installs tool version a second time without errors" { cd "$PROJECT_DIR" printf "dummy 1.0.0\nlegacy-dummy 1.0.0" >"$PROJECT_DIR/.tool-versions" run asdf install dummy 1.0.0 [ "$status" -eq 0 ] [ "$(cat "$ASDF_DIR/installs/dummy/1.0.0/version")" = "1.0.0" ] run asdf install [ "$status" -eq 0 ] [ "$(cat "$ASDF_DIR/installs/legacy-dummy/1.0.0/version")" = "1.0.0" ] } ================================================ FILE: test/latest_command.bats ================================================ #!/usr/bin/env bats load test_helpers setup() { setup_asdf_dir install_dummy_plugin install_dummy_legacy_plugin } teardown() { clean_asdf_dir } #################################################### #### plugin with bin/latest-stable #### #################################################### @test "[latest_command - dummy_plugin] shows latest stable version" { run asdf latest dummy [ "2.0.0" = "$output" ] [ "$status" -eq 0 ] } @test "[latest_command - dummy_plugin] shows latest stable version that matches the given string" { run asdf latest dummy 1 [ "1.1.0" = "$output" ] [ "$status" -eq 0 ] } @test "[latest_command - dummy_plugin] an invalid version should return an error" { run asdf latest dummy 3 [ "No compatible versions available (dummy 3)" = "$output" ] [ "$status" -eq 1 ] } #################################################### #### plugin without bin/latest-stable #### #################################################### @test "[latest_command - dummy_legacy_plugin] shows latest stable version" { run asdf latest legacy-dummy [ "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 [ "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 [ "No compatible versions available (legacy-dummy 3)" = "$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 [ "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 [ "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 [ "No compatible versions available (legacy-dummy 6)" = "$output" ] [ "$status" -eq 1 ] } ################################ #### latest --all #### ################################ @test "[latest_command - all plugins] shows the latest stable version of all plugins" { run asdf install dummy 2.0.0 run asdf install legacy-dummy 4.0.0 run asdf latest --all [ $'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 [ $'dummy\t2.0.0\tmissing\nlegacy-dummy\t5.1.0\tmissing' = "$output" ] [ "$status" -eq 0 ] } ================================================ FILE: test/list_command.bats ================================================ #!/usr/bin/env bats load test_helpers setup() { setup_asdf_dir install_dummy_plugin install_dummy_broken_plugin PROJECT_DIR="$HOME/project" mkdir -p "$PROJECT_DIR" } teardown() { clean_asdf_dir } @test "list_command should list plugins with installed versions" { run asdf install dummy 1.0.0 run asdf install dummy 1.1.0 run asdf list [[ "$output" == *$'dummy\n 1.0.0\n 1.1.0'* ]] [[ "$output" == *$'dummy-broken\n No versions installed'* ]] [ "$status" -eq 0 ] } @test "list_command should list plugins with installed versions and any selected versions marked with asterisk" { cd "$PROJECT_DIR" echo 'dummy 1.1.0' >>"$PROJECT_DIR/.tool-versions" run asdf install dummy 1.0.0 run asdf install dummy 1.1.0 run asdf list [[ "$output" == *$'dummy\n 1.0.0\n *1.1.0'* ]] [[ "$output" == *$'dummy-broken\n No versions installed'* ]] [ "$status" -eq 0 ] } @test "list_command should continue listing even when no version is installed for any of the plugins" { run install_mock_plugin "dummy" run install_mock_plugin "mummy" run install_mock_plugin "tummy" run asdf install dummy 1.0.0 run asdf install tummy 2.0.0 run asdf list [[ "$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 ] } @test "list_command with plugin should list installed versions" { run asdf install dummy 1.0.0 run asdf install dummy 1.1.0 run asdf list dummy [ $' 1.0.0\n 1.1.0' = "$output" ] [ "$status" -eq 0 ] } @test "list_command with version filters installed versions" { run asdf install dummy 1.0 run asdf install dummy 1.1 run asdf install dummy 2.0 run asdf list dummy 1 [ $' 1.0\n 1.1' = "$output" ] [ "$status" -eq 0 ] } @test "list_command with an invalid version should return an error" { run asdf install dummy 1.0 run asdf install dummy 1.1 run asdf list dummy 2 [ "No compatible versions installed (dummy 2)" = "$output" ] [ "$status" -eq 0 ] } @test "list_all_command lists available versions" { run asdf list all dummy [ $'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 [ $'1.0.0\n1.1.0' = "$output" ] [ "$status" -eq 0 ] } @test "list_all_command with an invalid version should return an error" { run asdf list all dummy 3 [ "No compatible versions available (dummy 3)" = "$output" ] [ "$status" -eq 1 ] } @test "list_all_command fails when list-all script exits with non-zero code" { run asdf list all dummy-broken [ "$status" -eq 1 ] [[ "$output" == "Plugin dummy-broken's list-all callback script failed with output:"* ]] } @test "list_all_command displays stderr then stdout when failing" { run asdf list all dummy-broken [[ "$output" == *"List-all failed!"* ]] [[ "$output" == *"Attempting to list versions" ]] } @test "list_all_command ignores stderr when completing successfully" { run asdf list all dummy [[ "$output" != *"ignore this error"* ]] } ================================================ FILE: test/non_existent_command.bats ================================================ #!/usr/bin/env bats load test_helpers setup() { setup_asdf_dir PROJECT_DIR="$HOME/project" mkdir -p "$PROJECT_DIR" } @test "should show help when no valid command is provided" { cd "$PROJECT_DIR" run asdf non-existent-command [ "$status" -eq 1 ] [[ $output == 'invalid command provided:'* ]] [[ $output =~ $'version: '[0-9]* ]] [[ $output == *$'MANAGE PLUGINS\n'* ]] [[ $output == *$'MANAGE TOOLS\n'* ]] [[ $output == *$'UTILS\n'* ]] [[ $output == *$'"Late but latest"\n-- Rajinikanth' ]] } ================================================ FILE: test/plugin_add_command.bats ================================================ #!/usr/bin/env bats # shellcheck disable=SC2030,SC2031 load test_helpers setup() { setup_asdf_dir } teardown() { clean_asdf_dir } @test "plugin_add command with plugin name matching all valid regex chars succeeds" { install_mock_plugin_repo "plugin_with-all-valid-chars-123" run asdf plugin add "plugin_with-all-valid-chars-123" "${BASE_DIR}/repo-plugin_with-all-valid-chars-123" [ "$status" -eq 0 ] run asdf plugin list [ "$output" = "plugin_with-all-valid-chars-123" ] } @test "plugin_add command with LANG=sv_SE.UTF-8 and plugin name matching all valid regex chars succeeds" { ORIGINAL_LANG="$LANG" LANG=sv_SE.UTF-8 install_mock_plugin_repo "plugin-with-w" # https://stackoverflow.com/questions/52570103/regular-expression-a-za-z-seems-to-not-include-letter-w-and-wA # https://github.com/asdf-vm/asdf/issues/1237 run asdf plugin add "plugin-with-w" "${BASE_DIR}/repo-plugin-with-w" [ "$status" -eq 0 ] run asdf plugin list [ "$output" = "plugin-with-w" ] LANG="$ORIGINAL_LANG" } @test "plugin_add command with plugin name not matching valid regex fails 1" { run asdf plugin add "invalid\$plugin\$name" [ "$status" -eq 1 ] [ "$output" = "invalid\$plugin\$name is invalid. Name may only contain lowercase letters, numbers, '_', and '-'" ] } @test "plugin_add command with plugin name not matching valid regex fails 2" { run asdf plugin add "#invalid#plugin#name" [ "$status" -eq 1 ] [ "$output" = "#invalid#plugin#name is invalid. Name may only contain lowercase letters, numbers, '_', and '-'" ] } @test "plugin_add command with plugin name not matching valid regex fails 3" { run asdf plugin add "Ruby" [ "$status" -eq 1 ] [ "$output" = "Ruby is invalid. Name may only contain lowercase letters, numbers, '_', and '-'" ] } @test "plugin_add command with no URL specified adds a plugin using repo" { run asdf plugin add "elixir" [ "$status" -eq 0 ] run asdf plugin list [ "$output" = "elixir" ] } @test "plugin_add command with no URL specified adds a plugin when short name repository is enabled" { export ASDF_CONFIG_DEFAULT_FILE="$HOME/.asdfrc" echo "disable_plugin_short_name_repository=no" >"$ASDF_CONFIG_DEFAULT_FILE" run asdf plugin add "elixir" [ "$status" -eq 0 ] local expected="elixir" run asdf plugin list [ "$output" = "$expected" ] } @test "plugin_add command with no URL specified fails to add a plugin when disabled" { export ASDF_CONFIG_DEFAULT_FILE="$HOME/.asdfrc" echo "disable_plugin_short_name_repository=yes" >"$ASDF_CONFIG_DEFAULT_FILE" local expected="Short-name plugin repository is disabled" run asdf plugin add "elixir" [ "$status" -eq 1 ] [ "$output" = "$expected" ] } @test "plugin_add command with URL specified adds a plugin using repo" { install_mock_plugin_repo "dummy" run asdf plugin add "dummy" "${BASE_DIR}/repo-dummy" [ "$status" -eq 0 ] run asdf plugin list # whitespace between 'elixir' and url is from printf %-15s %s format [ "$output" = "dummy" ] } @test "plugin_add command with URL specified twice returns success on second time" { install_mock_plugin_repo "dummy" run asdf plugin add "dummy" "${BASE_DIR}/repo-dummy" run asdf plugin add "dummy" "${BASE_DIR}/repo-dummy" [ "$status" -eq 0 ] [ "$output" = "Plugin named dummy already added" ] } @test "plugin_add command with no URL specified fails if the plugin doesn't exist" { run asdf plugin add "does-not-exist" [ "$status" -eq 1 ] echo "$output" | grep "plugin does-not-exist not found in repository" } @test "plugin_add command executes post-plugin add script" { install_mock_plugin_repo "dummy" run asdf plugin add "dummy" "${BASE_DIR}/repo-dummy" [ "$output" = "plugin add path=${ASDF_DIR}/plugins/dummy source_url=${BASE_DIR}/repo-dummy" ] } @test "plugin_add command executes configured pre hook (generic)" { install_mock_plugin_repo "dummy" cat >"$HOME/.asdfrc" <<-'EOM' pre_asdf_plugin_add = echo ADD ${@} EOM run asdf plugin add "dummy" "${BASE_DIR}/repo-dummy" local expected_output="ADD dummy plugin add path=${ASDF_DIR}/plugins/dummy source_url=${BASE_DIR}/repo-dummy" [ "$output" = "${expected_output}" ] } @test "plugin_add command executes configured pre hook (specific)" { install_mock_plugin_repo "dummy" cat >"$HOME/.asdfrc" <<-'EOM' pre_asdf_plugin_add_dummy = echo ADD EOM run asdf plugin add "dummy" "${BASE_DIR}/repo-dummy" local expected_output="ADD plugin add path=${ASDF_DIR}/plugins/dummy source_url=${BASE_DIR}/repo-dummy" [ "$output" = "${expected_output}" ] } @test "plugin_add command executes configured post hook (generic)" { install_mock_plugin_repo "dummy" cat >"$HOME/.asdfrc" <<-'EOM' post_asdf_plugin_add = echo ADD ${@} EOM run asdf plugin add "dummy" "${BASE_DIR}/repo-dummy" local expected_output="plugin add path=${ASDF_DIR}/plugins/dummy source_url=${BASE_DIR}/repo-dummy ADD dummy" [ "$output" = "${expected_output}" ] } @test "plugin_add command executes configured post hook (specific)" { install_mock_plugin_repo "dummy" cat >"$HOME/.asdfrc" <<-'EOM' post_asdf_plugin_add_dummy = echo ADD EOM run asdf plugin add "dummy" "${BASE_DIR}/repo-dummy" local expected_output="plugin add path=${ASDF_DIR}/plugins/dummy source_url=${BASE_DIR}/repo-dummy ADD" [ "$output" = "${expected_output}" ] } ================================================ FILE: test/plugin_extension_command.bats ================================================ #!/usr/bin/env bats # shellcheck disable=SC2016 load test_helpers setup() { setup_asdf_dir install_dummy_plugin local plugin_path plugin_path="$(get_plugin_path dummy)" mkdir -p "$plugin_path/lib/commands" } teardown() { clean_asdf_dir } @test "asdf help shows plugin extension commands" { local plugin_path listed_cmds plugin_path="$(get_plugin_path dummy)" touch "$plugin_path/lib/commands/command" touch "$plugin_path/lib/commands/command-foo" touch "$plugin_path/lib/commands/command-foo-bar" run asdf help [ "$status" -eq 0 ] echo "$output" | grep "PLUGIN dummy" # should present plugin section listed_cmds=$(echo "$output" | grep -c "asdf dummy") [ "$listed_cmds" -eq 3 ] echo "$output" | grep "asdf dummy foo-bar" } @test "asdf help shows extension commands for plugin with hyphens in the name" { cd "$PROJECT_DIR" plugin_name=dummy-hyphenated install_mock_plugin $plugin_name plugin_path="$(get_plugin_path $plugin_name)" mkdir -p "$plugin_path/lib/commands" touch "$plugin_path/lib/commands/command" touch "$plugin_path/lib/commands/command-foo" touch "$plugin_path/lib/commands/command-foo-bar" run asdf help [ "$status" -eq 0 ] [[ "$output" == *"PLUGIN $plugin_name"* ]] listed_cmds=$(grep -c "asdf $plugin_name" <<<"${output}") [[ $listed_cmds -eq 3 ]] [[ "$output" == *"asdf $plugin_name foo"* ]] [[ "$output" == *"asdf $plugin_name foo-bar"* ]] } @test "asdf can execute plugin bin commands" { plugin_path="$(get_plugin_path dummy)" # this plugin defines a new `asdf dummy foo` command cat <<'EOF' >"$plugin_path/lib/commands/command-foo" #!/usr/bin/env bash echo this is an executable $* EOF chmod +x "$plugin_path/lib/commands/command-foo" expected="this is an executable bar" run asdf cmd dummy foo bar [ "$status" -eq 0 ] [ "$output" = "$expected" ] } # No longer supported. If you want to do this you'll need to manual source the # file containing the functions you want via relative path. #@test "asdf can source plugin bin scripts" { # plugin_path="$(get_plugin_path dummy)" # # this plugin defines a new `asdf dummy foo` command # echo '#!/usr/bin/env bash # echo sourced script has asdf utils $(get_plugin_path dummy) $*' >"$plugin_path/lib/commands/command-foo" # expected="sourced script has asdf utils $plugin_path bar" # run asdf cmd dummy foo bar # [ "$status" -eq 0 ] # [ "$output" = "$expected" ] #} @test "asdf can execute plugin default command without arguments" { plugin_path="$(get_plugin_path dummy)" # this plugin defines a new `asdf dummy` command cat <<'EOF' >"$plugin_path/lib/commands/command" #!/usr/bin/env bash echo hello EOF chmod +x "$plugin_path/lib/commands/command" expected="hello" run asdf cmd dummy [ "$status" -eq 0 ] [ "$output" = "$expected" ] } @test "asdf can execute plugin default command with arguments" { plugin_path="$(get_plugin_path dummy)" # this plugin defines a new `asdf dummy` command cat <<'EOF' >"$plugin_path/lib/commands/command" #!/usr/bin/env bash echo hello $* EOF chmod +x "$plugin_path/lib/commands/command" expected="hello world" run asdf cmd dummy world [ "$status" -eq 0 ] [ "$output" = "$expected" ] } @test "asdf execute plugin default command unsets ASDF_INSTALL_TYPE ASDF_INSTALL_VERSION and ASDF_INSTALL_PATH env variables" { export ASDF_INSTALL_VERSION=1.2.3 export ASDF_INSTALL_TYPE=version export ASDF_INSTALL_PATH=/somewhere plugin_path="$(get_plugin_path dummy)" # this plugin defines a new `asdf dummy` command cat <<'EOF' >"$plugin_path/lib/commands/command" #!/usr/bin/env bash /usr/bin/env EOF chmod +x "$plugin_path/lib/commands/command" run asdf cmd dummy [ "$status" -eq 0 ] [ "0" -eq "$(echo "$output" | grep -c "^ASDF_INSTALL_")" ] } ================================================ FILE: test/plugin_list_all_command.bats ================================================ #!/usr/bin/env bats # shellcheck disable=SC2030,SC2031 load test_helpers setup() { setup_asdf_dir setup_repo install_dummy_plugin } teardown() { clean_asdf_dir } @test "plugin_list_all should exit before syncing the plugin repo if disabled" { export ASDF_CONFIG_DEFAULT_FILE="$HOME/.asdfrc" echo 'disable_plugin_short_name_repository=yes' >"$ASDF_CONFIG_DEFAULT_FILE" local expected="Short-name plugin repository is disabled" run asdf plugin list all [ "$status" -eq 1 ] [ "$output" = "$expected" ] } @test "plugin_list_all should sync repo when check_duration set to 0" { export ASDF_CONFIG_DEFAULT_FILE="$HOME/.asdfrc" echo 'plugin_repository_last_check_duration = 0' >"$ASDF_CONFIG_DEFAULT_FILE" local expected_plugins_list="\ bar http://example.com/bar dummy http://example.com/dummy foo http://example.com/foo" run asdf plugin list all [ "$status" -eq 0 ] [[ "$output" == *"$expected_plugins_list"* ]] } @test "plugin_list_all no immediate repo sync expected because check_duration is greater than 0" { export ASDF_CONFIG_DEFAULT_FILE="$HOME/.asdfrc" echo 'plugin_repository_last_check_duration = 10' >"$ASDF_CONFIG_DEFAULT_FILE" local expected="\ bar http://example.com/bar dummy http://example.com/dummy foo http://example.com/foo" run asdf plugin list all [ "$status" -eq 0 ] [ "$output" = "$expected" ] } @test "plugin_list_all skips repo sync because check_duration is set to never" { export ASDF_CONFIG_DEFAULT_FILE="$HOME/.asdfrc" echo 'plugin_repository_last_check_duration = never' >"$ASDF_CONFIG_DEFAULT_FILE" local expected="\ bar http://example.com/bar dummy http://example.com/dummy foo http://example.com/foo" run asdf plugin list all [ "$status" -eq 0 ] [ "$output" = "$expected" ] } @test "plugin_list_all list all plugins in the repository" { local expected="\ bar http://example.com/bar dummy http://example.com/dummy foo http://example.com/foo" run asdf plugin list all [ "$status" -eq 0 ] [ "$output" = "$expected" ] } ================================================ FILE: test/plugin_remove_command.bats ================================================ #!/usr/bin/env bats load test_helpers setup() { setup_asdf_dir install_dummy_plugin } teardown() { clean_asdf_dir } @test "plugin_remove command removes the plugin directory" { run asdf install dummy 1.0 [ "$status" -eq 0 ] [ -d "$ASDF_DIR/downloads/dummy" ] run asdf plugin remove "dummy" [ "$status" -eq 0 ] [ ! -d "$ASDF_DIR/downloads/dummy" ] } @test "plugin_remove command fails if the plugin doesn't exist" { run asdf plugin remove "does-not-exist" [ "$status" -eq 1 ] echo "$output" | grep "No such plugin: does-not-exist" } ================================================ FILE: test/plugin_test_command.bats ================================================ #!/usr/bin/env bats load test_helpers setup() { setup_asdf_dir install_mock_plugin_repo "dummy" } teardown() { clean_asdf_dir } @test "plugin_test_command with no URL specified prints an error" { run asdf plugin test "elixir" [ "$status" -eq 1 ] [ "$output" = "FAILED: please provide a plugin name and url" ] } @test "plugin_test_command with no name or URL specified prints an error" { run asdf plugin test [ "$status" -eq 1 ] [ "$output" = "FAILED: please provide a plugin name and url" ] } @test "plugin_test_command works with no options provided" { run asdf plugin test dummy "${BASE_DIR}/repo-dummy" [ "$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 [ "$status" -eq 0 ] } ================================================ FILE: test/plugin_update_command.bats ================================================ #!/usr/bin/env bats load test_helpers setup() { setup_asdf_dir install_mock_plugin_repo "dummy" run asdf plugin add "dummy" "${BASE_DIR}/repo-dummy" } teardown() { clean_asdf_dir } @test "asdf plugin update should pull latest default branch (refs/remotes/origin/HEAD) for plugin" { run asdf plugin update dummy repo_head="$(git --git-dir "$ASDF_DIR/plugins/dummy/.git" --work-tree "$ASDF_DIR/plugins/dummy" rev-parse --abbrev-ref HEAD)" [ "$status" -eq 0 ] [[ "$output" =~ "updated dummy to ref refs/heads/master"* ]] [ "$repo_head" = "master" ] } #@test "asdf plugin update should pull latest default branch (refs/remotes/origin/HEAD) for plugin even if default branch changes" { # install_mock_plugin_repo "dummy-remote" # remote_dir="$BASE_DIR/repo-dummy-remote" # # set HEAD to refs/head/main in dummy-remote # git -C "${remote_dir}" checkout -b main # # track & fetch remote repo (dummy-remote) in plugin (dummy) # git --git-dir "$ASDF_DIR/plugins/dummy/.git" --work-tree "$ASDF_DIR/plugins/dummy" remote remove origin # git --git-dir "$ASDF_DIR/plugins/dummy/.git" --work-tree "$ASDF_DIR/plugins/dummy" remote add origin "$remote_dir" # git --git-dir "$ASDF_DIR/plugins/dummy/.git" --work-tree "$ASDF_DIR/plugins/dummy" fetch origin # run asdf plugin update dummy # repo_head="$(git --git-dir "$ASDF_DIR/plugins/dummy/.git" --work-tree "$ASDF_DIR/plugins/dummy" rev-parse --abbrev-ref HEAD)" # [ "$status" -eq 0 ] # [[ "$output" =~ "Updating dummy to main"* ]] # [ "$repo_head" = "main" ] #} #@test "asdf plugin update should pull latest default branch (refs/remotes/origin/HEAD) for plugin even if the default branch contains a forward slash" { # install_mock_plugin_repo "dummy-remote" # remote_dir="$BASE_DIR/repo-dummy-remote" # # set HEAD to refs/head/my/default in dummy-remote # git -C "${remote_dir}" checkout -b my/default # # track & fetch remote repo (dummy-remote) in plugin (dummy) # git --git-dir "$ASDF_DIR/plugins/dummy/.git" --work-tree "$ASDF_DIR/plugins/dummy" remote remove origin # git --git-dir "$ASDF_DIR/plugins/dummy/.git" --work-tree "$ASDF_DIR/plugins/dummy" remote add origin "$remote_dir" # git --git-dir "$ASDF_DIR/plugins/dummy/.git" --work-tree "$ASDF_DIR/plugins/dummy" fetch origin # run asdf plugin update dummy # repo_head="$(git --git-dir "$ASDF_DIR/plugins/dummy/.git" --work-tree "$ASDF_DIR/plugins/dummy" rev-parse --abbrev-ref HEAD)" # [ "$status" -eq 0 ] # [[ "$output" =~ "Updating dummy to my/default"* ]] # [ "$repo_head" = "my/default" ] #} #@test "asdf plugin update should pull latest default branch (refs/remotes/origin/HEAD) for plugin even if already set to specific ref" { # # set plugin to specific sha # current_sha="$(git --git-dir "${BASE_DIR}/repo-dummy/.git" --work-tree "$BASE_DIR/repo-dummy" rev-parse HEAD)" # run asdf plugin update dummy "${current_sha}" # # setup mock plugin remote # install_mock_plugin_repo "dummy-remote" # remote_dir="$BASE_DIR/repo-dummy-remote" # # set HEAD to refs/head/main in dummy-remote # git -C "${remote_dir}" checkout -b main # # track & fetch remote repo (dummy-remote) in plugin (dummy) # git --git-dir "$ASDF_DIR/plugins/dummy/.git" --work-tree "$ASDF_DIR/plugins/dummy" remote remove origin # git --git-dir "$ASDF_DIR/plugins/dummy/.git" --work-tree "$ASDF_DIR/plugins/dummy" remote add origin "$remote_dir" # git --git-dir "$ASDF_DIR/plugins/dummy/.git" --work-tree "$ASDF_DIR/plugins/dummy" fetch origin # # update plugin to the default branch # run asdf plugin update dummy # repo_head="$(git --git-dir "$ASDF_DIR/plugins/dummy/.git" --work-tree "$ASDF_DIR/plugins/dummy" rev-parse --abbrev-ref HEAD)" # [ "$status" -eq 0 ] # [[ "$output" =~ "Updating dummy to main"* ]] # [ "$repo_head" = "main" ] #} @test "asdf plugin update should not remove plugin versions" { run asdf install dummy 1.1 [ "$status" -eq 0 ] [ "$(cat "$ASDF_DIR/installs/dummy/1.1/version")" = "1.1" ] run asdf plugin update dummy [ "$status" -eq 0 ] [ -f "$ASDF_DIR/installs/dummy/1.1/version" ] run asdf plugin update --all [ "$status" -eq 0 ] [ -f "$ASDF_DIR/installs/dummy/1.1/version" ] } @test "asdf plugin update should not remove plugins" { # dummy plugin is already installed run asdf plugin update dummy [ "$status" -eq 0 ] [ -d "$ASDF_DIR/plugins/dummy" ] run asdf plugin update --all [ "$status" -eq 0 ] [ -d "$ASDF_DIR/plugins/dummy" ] } @test "asdf plugin update should not remove shims" { run asdf install dummy 1.1 [ -f "$ASDF_DIR/shims/dummy" ] run asdf plugin update dummy [ "$status" -eq 0 ] [ -f "$ASDF_DIR/shims/dummy" ] run asdf plugin update --all [ "$status" -eq 0 ] [ -f "$ASDF_DIR/shims/dummy" ] } # TODO: Get these tests passing #@test "asdf plugin update done for all plugins" { # local command="asdf plugin update --all" # # Count the number of update processes remaining after the update command is completed. # run bash -c "${command} >/dev/null && ps -o 'ppid,args' | awk '{if(\$1==1 && \$0 ~ /${command}/ ) print}' | wc -l" # [[ 0 -eq "$output" ]] #} #@test "asdf plugin update executes post-plugin update script" { # local plugin_path # plugin_path="$(get_plugin_path dummy)" # old_ref="$(git --git-dir "$plugin_path/.git" --work-tree "$plugin_path" rev-parse --short HEAD)" # run asdf plugin update dummy # new_ref="$(git --git-dir "$plugin_path/.git" --work-tree "$plugin_path" rev-parse --short HEAD)" # local expected_output="plugin updated path=${plugin_path} old git-ref=${old_ref} new git-ref=${new_ref}" # [[ "$output" = *"${expected_output}" ]] #} #@test "asdf plugin update executes post-plugin update script if git-ref updated" { # local plugin_path # plugin_path="$(get_plugin_path dummy)" # old_ref="$(git --git-dir "$plugin_path/.git" --work-tree "$plugin_path" rev-parse --short HEAD)" # # setup mock plugin remote # install_mock_plugin_repo "dummy-remote" # remote_dir="$BASE_DIR/repo-dummy-remote" # # set HEAD to refs/head/main in dummy-remote # git -C "${remote_dir}" checkout -b main # # track & fetch remote repo (dummy-remote) in plugin (dummy) # git --git-dir "$plugin_path/.git" --work-tree "$plugin_path" remote remove origin # git --git-dir "$plugin_path/.git" --work-tree "$plugin_path" remote add origin "$remote_dir" # git --git-dir "$plugin_path/.git" --work-tree "$plugin_path" fetch origin # # update plugin to the default branch # run asdf plugin update dummy # new_ref="$(git --git-dir "$plugin_path/.git" --work-tree "$plugin_path" rev-parse --short HEAD)" # local expected_output="plugin updated path=${plugin_path} old git-ref=${old_ref} new git-ref=${new_ref}" # [[ "$output" = *"${expected_output}" ]] #} #@test "asdf plugin update executes configured pre hook (generic)" { # cat >"$HOME/.asdfrc" <<-'EOM' #pre_asdf_plugin_update = echo UPDATE ${@} #EOM # local plugin_path # plugin_path="$(get_plugin_path dummy)" # old_ref="$(git --git-dir "$plugin_path/.git" --work-tree "$plugin_path" rev-parse --short HEAD)" # run asdf plugin update dummy # new_ref="$(git --git-dir "$plugin_path/.git" --work-tree "$plugin_path" rev-parse --short HEAD)" # local expected_output="plugin updated path=${plugin_path} old git-ref=${old_ref} new git-ref=${new_ref}" # [[ "$output" = *"UPDATE dummy"*"${expected_output}" ]] #} #@test "asdf plugin update executes configured pre hook (specific)" { # cat >"$HOME/.asdfrc" <<-'EOM' #pre_asdf_plugin_update_dummy = echo UPDATE #EOM # local plugin_path # plugin_path="$(get_plugin_path dummy)" # old_ref="$(git --git-dir "$plugin_path/.git" --work-tree "$plugin_path" rev-parse --short HEAD)" # run asdf plugin update dummy # new_ref="$(git --git-dir "$plugin_path/.git" --work-tree "$plugin_path" rev-parse --short HEAD)" # local expected_output="plugin updated path=${plugin_path} old git-ref=${old_ref} new git-ref=${new_ref}" # [[ "$output" = *"UPDATE"*"${expected_output}" ]] #} #@test "asdf plugin update executes configured post hook (generic)" { # cat >"$HOME/.asdfrc" <<-'EOM' #post_asdf_plugin_update = echo UPDATE ${@} #EOM # local plugin_path # plugin_path="$(get_plugin_path dummy)" # old_ref="$(git --git-dir "$plugin_path/.git" --work-tree "$plugin_path" rev-parse --short HEAD)" # run asdf plugin update dummy # new_ref="$(git --git-dir "$plugin_path/.git" --work-tree "$plugin_path" rev-parse --short HEAD)" # local expected_output="plugin updated path=${plugin_path} old git-ref=${old_ref} new git-ref=${new_ref} #UPDATE dummy" # [[ "$output" = *"${expected_output}" ]] #} #@test "asdf plugin update executes configured post hook (specific)" { # cat >"$HOME/.asdfrc" <<-'EOM' #post_asdf_plugin_update_dummy = echo UPDATE #EOM # local plugin_path # plugin_path="$(get_plugin_path dummy)" # old_ref="$(git --git-dir "$plugin_path/.git" --work-tree "$plugin_path" rev-parse --short HEAD)" # run asdf plugin update dummy # new_ref="$(git --git-dir "$plugin_path/.git" --work-tree "$plugin_path" rev-parse --short HEAD)" # local expected_output="plugin updated path=${plugin_path} old git-ref=${old_ref} new git-ref=${new_ref} #UPDATE #updated dummy to ref refs/heads/master" # [[ "$output" = *"${expected_output}" ]] #} # No longer supported #@test "asdf plugin update prints the location of plugin (specific)" { # local plugin_path # plugin_path="$(get_plugin_path dummy)" # run asdf plugin update dummy # local expected_output="Location of dummy plugin: $plugin_path" # [[ "$output" == *"$expected_output"* ]] #} ================================================ FILE: test/remove_command.bats ================================================ #!/usr/bin/env bats load test_helpers setup() { setup_asdf_dir } teardown() { clean_asdf_dir } @test "plugin_remove_command removes a plugin" { install_dummy_plugin run asdf plugin remove "dummy" [ "$status" -eq 0 ] [ "$output" = "plugin-remove ${ASDF_DIR}/plugins/dummy" ] } @test "plugin_remove_command should exit with 1 when not passed any arguments" { run asdf plugin remove [ "$status" -eq 1 ] [ "$output" = "No plugin given" ] } @test "plugin_remove_command should exit with 1 when passed invalid plugin name" { run asdf plugin remove "does-not-exist" [ "$status" -eq 1 ] [ "$output" = "No such plugin: does-not-exist" ] } @test "plugin_remove_command should remove installed versions" { install_dummy_plugin run asdf install dummy 1.0 [ "$status" -eq 0 ] [ -d "$ASDF_DIR/installs/dummy" ] run asdf plugin remove dummy [ "$status" -eq 0 ] [ ! -d "$ASDF_DIR/installs/dummy" ] } @test "plugin_remove_command should also remove shims for that plugin" { install_dummy_plugin run asdf install dummy 1.0 [ "$status" -eq 0 ] [ -f "$ASDF_DIR/shims/dummy" ] run asdf plugin remove dummy [ "$status" -eq 0 ] [ ! -f "$ASDF_DIR/shims/dummy" ] } # Disabled this test because it while the title is correct, the test code sets # and invalid state (shim unattached to any existing plugin) that is corrected # by asdf reshim removing the invalid shim, and that fails this test, even # though it's the correct behavior #@test "plugin_remove_command should not remove unrelated shims" { # install_dummy_plugin # run asdf install dummy 1.0 # # make an unrelated shim # echo "# asdf-plugin: gummy" >"$ASDF_DIR/shims/gummy" # run asdf plugin remove dummy # [ "$status" -eq 0 ] # # unrelated shim should exist # [ -f "$ASDF_DIR/shims/gummy" ] #} @test "plugin_remove_command executes pre-plugin-remove script" { install_dummy_plugin run asdf plugin remove dummy [ "$output" = "plugin-remove ${ASDF_DIR}/plugins/dummy" ] } @test "plugin_remove_command executes configured pre hook (generic)" { install_dummy_plugin cat >"$HOME/.asdfrc" <<-'EOM' pre_asdf_plugin_remove = echo REMOVE ${@} EOM run asdf plugin remove dummy local expected_output="REMOVE dummy plugin-remove ${ASDF_DIR}/plugins/dummy" [ "$output" = "${expected_output}" ] } @test "plugin_remove_command executes configured pre hook (specific)" { install_dummy_plugin cat >"$HOME/.asdfrc" <<-'EOM' pre_asdf_plugin_remove_dummy = echo REMOVE EOM run asdf plugin remove dummy local expected_output="REMOVE plugin-remove ${ASDF_DIR}/plugins/dummy" [ "$output" = "${expected_output}" ] } @test "plugin_remove_command executes configured post hook (generic)" { install_dummy_plugin cat >"$HOME/.asdfrc" <<-'EOM' post_asdf_plugin_remove = echo REMOVE ${@} EOM run asdf plugin remove dummy local expected_output="plugin-remove ${ASDF_DIR}/plugins/dummy REMOVE dummy" [ "$output" = "${expected_output}" ] } @test "plugin_remove_command executes configured post hook (specific)" { install_dummy_plugin cat >"$HOME/.asdfrc" <<-'EOM' post_asdf_plugin_remove_dummy = echo REMOVE EOM run asdf plugin remove dummy local expected_output="plugin-remove ${ASDF_DIR}/plugins/dummy REMOVE" [ "$output" = "${expected_output}" ] } ================================================ FILE: test/reshim_command.bats ================================================ #!/usr/bin/env bats # shellcheck disable=SC2012 load test_helpers setup() { setup_asdf_dir install_dummy_plugin PROJECT_DIR="$HOME/project" mkdir "$PROJECT_DIR" } teardown() { clean_asdf_dir } @test "reshim should print error when plugin with name does not exist" { run asdf reshim non-existent 1.0 [ "$status" -eq 1 ] [ "$output" = "No such plugin: non-existent" ] } @test "reshim should allow prefixes of other versions" { run asdf install dummy 1.0.1 run asdf install dummy 1.0 run asdf reshim [ "$status" -eq 0 ] run grep "asdf-plugin: dummy 1.0.1" "$ASDF_DIR/shims/dummy" [ "$status" -eq 0 ] run grep "asdf-plugin: dummy 1.0\$" "$ASDF_DIR/shims/dummy" [ "$status" -eq 0 ] } @test "reshim command should remove shims of removed binaries" { run asdf install dummy 1.0 [ "$status" -eq 0 ] [ -f "$ASDF_DIR/shims/dummy" ] run asdf reshim dummy [ "$status" -eq 0 ] [ -f "$ASDF_DIR/shims/dummy" ] run rm "$ASDF_DIR/installs/dummy/1.0/bin/dummy" run asdf reshim dummy [ "$status" -eq 0 ] [ ! -f "$ASDF_DIR/shims/dummy" ] } @test "reshim should remove metadata of removed binaries" { run asdf install dummy 1.0 run asdf install dummy 1.1 run rm "$ASDF_DIR/installs/dummy/1.0/bin/dummy" run asdf reshim dummy [ "$status" -eq 0 ] [ -f "$ASDF_DIR/shims/dummy" ] run grep "asdf-plugin: dummy 1.0" "$ASDF_DIR/shims/dummy" [ "$status" -eq 1 ] run grep "asdf-plugin: dummy 1.1" "$ASDF_DIR/shims/dummy" [ "$status" -eq 0 ] } @test "reshim should not remove metadata of removed prefix versions" { run asdf install dummy 1.0 run asdf install dummy 1.0.1 run rm "$ASDF_DIR/installs/dummy/1.0/bin/dummy" run asdf reshim dummy [ "$status" -eq 0 ] [ -f "$ASDF_DIR/shims/dummy" ] run grep "asdf-plugin: dummy 1.0.1" "$ASDF_DIR/shims/dummy" [ "$status" -eq 0 ] } @test "reshim should not duplicate shims" { cd "$PROJECT_DIR" run asdf install dummy 1.0 run asdf install dummy 1.1 [ "$status" -eq 0 ] [ -f "$ASDF_DIR/shims/dummy" ] run rm "$ASDF_DIR/shims/"* [ "$status" -eq 0 ] [ "0" -eq "$(ls "$ASDF_DIR/shims/"dummy* | wc -l)" ] run asdf reshim dummy [ "$status" -eq 0 ] [ "1" -eq "$(ls "$ASDF_DIR/shims/"dummy* | wc -l)" ] run asdf reshim dummy [ "$status" -eq 0 ] [ "1" -eq "$(ls "$ASDF_DIR/shims/"dummy* | wc -l)" ] } @test "reshim should create shims only for files and not folders" { cd "$PROJECT_DIR" run asdf install dummy 1.0 run asdf install dummy 1.1 [ "$status" -eq 0 ] [ -f "$ASDF_DIR/shims/dummy" ] [ ! -f "$ASDF_DIR/shims/subdir" ] run rm "$ASDF_DIR/shims/"* [ "$status" -eq 0 ] [ "0" -eq "$(ls "$ASDF_DIR/shims/"dummy* | wc -l)" ] [ "0" -eq "$(ls "$ASDF_DIR/shims/"subdir* | wc -l)" ] run asdf reshim dummy [ "$status" -eq 0 ] [ "1" -eq "$(ls "$ASDF_DIR/shims/"dummy* | wc -l)" ] [ "0" -eq "$(ls "$ASDF_DIR/shims/"subdir* | wc -l)" ] } @test "reshim without arguments reshims all installed plugins" { run asdf install dummy 1.0 run rm "$ASDF_DIR/shims/"* [ "$status" -eq 0 ] [ "0" -eq "$(ls "$ASDF_DIR/shims/"dummy* | wc -l)" ] run asdf reshim [ "$status" -eq 0 ] [ "1" -eq "$(ls "$ASDF_DIR/shims/"dummy* | wc -l)" ] } @test "reshim command executes configured pre hook" { run asdf install dummy 1.0 cat >"$HOME/.asdfrc" <<-'EOM' pre_asdf_reshim_dummy = echo RESHIM EOM run asdf reshim dummy 1.0 [ "$output" = "RESHIM" ] } @test "reshim command executes configured post hook" { run asdf install dummy 1.0 cat >"$HOME/.asdfrc" <<-'EOM' post_asdf_reshim_dummy = echo RESHIM EOM run asdf reshim dummy 1.0 [ "$output" = "RESHIM" ] } # Fixes https://github.com/asdf-vm/asdf/issues/1115 # (Issue with executable_name changing after homebre updates) @test "reshim should rewrite the shim file except the version list" { run asdf install dummy 1.0 local dummy_shim dummy_shim="$ASDF_DIR/shims/dummy" sed -i.bak -e 's/exec /exec \/borked_path_due_to_homebrew_update/' "$dummy_shim" run grep 'borked_path_due_to_homebrew_update' "$dummy_shim" # Sanity check [ "$status" -eq 0 ] run asdf reshim dummy "path:$ASDF_DIR/installs/dummy/path" 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 ] } ================================================ FILE: test/setup_suite.bash ================================================ setup_suite() { # Unset ASDF_DIR because it may already be set by the users shell, and some # tests fail when it is set to something other than the temp dir. unset ASDF_DIR # Also unset below variables, because in users shell shimmed commands # (include bats) export them by determining user's real HOME. unset ASDF_DATA_DIR unset ASDF_CONFIG_FILE # Set an agnostic Git configuration directory to prevent personal # configuration from interfering with the tests export GIT_CONFIG_GLOBAL=/dev/null } ================================================ FILE: test/shim_env_command.bats ================================================ #!/usr/bin/env bats # shellcheck disable=SC2164 load test_helpers setup() { setup_asdf_dir install_dummy_plugin PROJECT_DIR="$HOME/project" mkdir -p "$PROJECT_DIR" cd "$PROJECT_DIR" # asdf lib needed to run generated shims cp -rf "$BATS_TEST_DIRNAME"/../{bin,lib} "$ASDF_DIR/" } teardown() { clean_asdf_dir } @test "asdf env without argument should display help" { run asdf env [ "$status" -eq 1 ] echo "$output" | grep "usage: asdf env <command>" } @test "asdf env should execute under the environment used for a shim" { echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions" run asdf install run asdf env dummy which dummy [ "$status" -eq 0 ] [ "$output" = "$ASDF_DIR/installs/dummy/1.0/bin/dummy" ] } @test "asdf env should execute under plugin custom environment used for a shim" { echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions" run asdf install echo '#!/usr/bin/env bash export FOO=bar' >"$ASDF_DIR/plugins/dummy/bin/exec-env" chmod +x "$ASDF_DIR/plugins/dummy/bin/exec-env" run asdf env dummy [ "$status" -eq 0 ] echo "$output" | grep 'FOO=bar' } @test "asdf env should pass through any already defined environment variables to a shim" { echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions" export MYTESTENV=exec-env-pass-through run asdf install echo '#!/usr/bin/env bash export FOO=bar' >"$ASDF_DIR/plugins/dummy/bin/exec-env" chmod +x "$ASDF_DIR/plugins/dummy/bin/exec-env" run asdf env dummy [ "$status" -eq 0 ] echo "$output" | grep 'FOO=bar' echo "$output" | grep 'MYTESTENV=exec-env-pass-through' } @test "asdf env should print error when plugin version lacks the specified executable" { echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions" run asdf install echo '#!/usr/bin/env bash export FOO=bar' >"$ASDF_DIR/plugins/dummy/bin/exec-env" chmod +x "$ASDF_DIR/plugins/dummy/bin/exec-env" echo "dummy system" >"$PROJECT_DIR/.tool-versions" run asdf env dummy [ "$status" -eq 1 ] [ "$output" = "No executable dummy found for current version. Please select a different version or install dummy manually for the current version" ] } @test "asdf env should ignore plugin custom environment on system version" { echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions" run asdf install echo '#!/usr/bin/env bash export FOO=bar' >"$ASDF_DIR/plugins/dummy/bin/exec-env" chmod +x "$ASDF_DIR/plugins/dummy/bin/exec-env" # Create a "system" dummy executable echo '#!/usr/bin/env bash echo "system dummy"' >"$ASDF_BIN/dummy" chmod +x "$ASDF_BIN/dummy" echo "dummy system" >"$PROJECT_DIR/.tool-versions" run asdf env dummy [ "$status" -eq 0 ] run grep 'FOO=bar' <<<"$output" [ "$output" = "" ] [ "$status" -eq 1 ] run asdf env dummy which dummy [ "$output" = "$ASDF_BIN/dummy" ] [ "$status" -eq 0 ] # Remove "system" dummy executable rm "$ASDF_BIN/dummy" } @test "asdf env should set PATH correctly" { echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions" run asdf install run asdf env dummy [ "$status" -eq 0 ] # Should set path path_line=$(echo "$output" | grep '^PATH=') [ "$path_line" != "" ] # Should not contain duplicate colon run grep -q '::' <<<"$path_line" [ "$status" -ne 0 ] } ================================================ FILE: test/shim_exec.bats ================================================ #!/usr/bin/env bats # shellcheck disable=SC2016,SC2164 load test_helpers setup() { setup_asdf_dir install_dummy_plugin PROJECT_DIR="$HOME/project" mkdir -p "$PROJECT_DIR" cd "$PROJECT_DIR" # asdf lib needed to run generated shims cp -rf "$BATS_TEST_DIRNAME"/../{bin,lib} "$ASDF_DIR/" } teardown() { clean_asdf_dir } @test "asdf exec without argument should display help" { run asdf exec [ "$status" -eq 1 ] echo "$output" | grep "usage: asdf exec <command>" } @test "asdf exec should pass all arguments to executable" { echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions" run asdf install run asdf exec dummy world hello [ "$output" = "This is Dummy 1.0! hello world" ] [ "$status" -eq 0 ] } @test "asdf exec should pass all arguments including flags to executable" { echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions" run asdf install run asdf exec dummy --mytestflag hello [ "$output" = "This is Dummy 1.0! hello --mytestflag" ] [ "$status" -eq 0 ] } @test "asdf exec should pass all arguments to executable even if shim is not in PATH" { echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions" run asdf install path=$(echo "$PATH" | sed -e "s|$(asdf_data_dir)/shims||g; s|::|:|g") run env PATH="$path" which dummy [ "$output" = "" ] [ "$status" -eq 1 ] run env PATH="$path" asdf exec dummy world hello [ "$output" = "This is Dummy 1.0! hello world" ] [ "$status" -eq 0 ] } @test "shim exec should pass all arguments to executable" { echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions" run asdf install run "$ASDF_DIR/shims/dummy" world hello [ "$output" = "This is Dummy 1.0! hello world" ] [ "$status" -eq 0 ] } @test "shim exec should pass stdin to executable" { echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions" run asdf install echo "#!/usr/bin/env bash tr [:lower:] [:upper:]" >"$ASDF_DIR/installs/dummy/1.0/bin/upper" chmod +x "$ASDF_DIR/installs/dummy/1.0/bin/upper" run asdf reshim dummy 1.0 run echo "$(echo hello | "$ASDF_DIR/shims/upper")" [ "$output" = "HELLO" ] [ "$status" -eq 0 ] } @test "shim exec should fail if no version is selected" { run asdf install dummy 1.0 touch "$PROJECT_DIR/.tool-versions" run "$ASDF_DIR/shims/dummy" world hello [ "$status" -eq 126 ] echo "$output" | grep -q "No version is set for command dummy" 2>/dev/null } @test "shim exec should suggest which plugin to use when no version is selected" { run asdf install dummy 1.0 run asdf install dummy 2.0.0 touch "$PROJECT_DIR/.tool-versions" run --separate-stderr "$ASDF_DIR/shims/dummy" world hello [ "$status" -eq 126 ] # shellcheck disable=SC2154 echo "${stderr:?}" | grep -q "No version is set for command dummy" 2>/dev/null echo "${stderr:?}" | grep -q "Consider adding one of the following versions in your config file at $PROJECT_DIR/.tool-versions" 2>/dev/null echo "${stderr:?}" | grep -q "dummy 1.0" 2>/dev/null echo "${stderr:?}" | grep -q "dummy 2.0.0" 2>/dev/null } @test "shim exec should suggest different plugins providing same tool when no version is selected" { # Another fake plugin with 'dummy' executable cp -rf "$ASDF_DIR/plugins/dummy" "$ASDF_DIR/plugins/mummy" run asdf install dummy 1.0 run asdf install mummy 3.0 touch "$PROJECT_DIR/.tool-versions" run "$ASDF_DIR/shims/dummy" world hello [ "$status" -eq 126 ] echo "$output" | grep -q "No version is set for command dummy" 2>/dev/null echo "$output" | grep -q "Consider adding one of the following versions in your config file at $PROJECT_DIR/.tool-versions" 2>/dev/null echo "$output" | grep -q "dummy 1.0" 2>/dev/null echo "$output" | grep -q "mummy 3.0" 2>/dev/null } # No longer possible for shim to specify version that isn't installed because # shims are re-generated after every install and uninstall. #@test "shim exec should suggest to install missing version" { # run asdf install dummy 1.0 # echo "dummy 2.0.0 1.3" >"$PROJECT_DIR/.tool-versions" # run "$ASDF_DIR/shims/dummy" world hello # [ "$status" -eq 126 ] # echo "$output" | grep -q "No preset version installed for command dummy" 2>/dev/null # echo "$output" | grep -q "Please install a version by running one of the following:" 2>/dev/null # echo "$output" | grep -q "asdf install dummy 2.0.0" 2>/dev/null # echo "$output" | grep -q "asdf install dummy 1.3" 2>/dev/null # echo "$output" | grep -q "or add one of the following versions in your config file at $PROJECT_DIR/.tool-versions" 2>/dev/null # echo "$output" | grep -q "dummy 1.0" 2>/dev/null #} @test "shim exec should execute first plugin that is installed and set" { run asdf install dummy 2.0.0 run asdf install dummy 3.0 echo "dummy 1.0 3.0 2.0.0" >"$PROJECT_DIR/.tool-versions" run "$ASDF_DIR/shims/dummy" world hello [ "$status" -eq 0 ] echo "$output" | grep -q "This is Dummy 3.0! hello world" 2>/dev/null } @test "shim exec should only use the first version found for a plugin" { run asdf install dummy 3.0 echo "dummy 3.0" >"$PROJECT_DIR/.tool-versions" echo "dummy 1.0" >>"$PROJECT_DIR/.tool-versions" run "$ASDF_DIR/shims/dummy" world hello [ "$status" -eq 0 ] echo "$output" | grep -q "This is Dummy 3.0! hello world" 2>/dev/null } @test "shim exec should determine correct executable on two projects using different plugins that provide the same tool" { # Another fake plugin with 'dummy' executable cp -rf "$ASDF_DIR/plugins/dummy" "$ASDF_DIR/plugins/mummy" sed -i -e 's/Dummy/Mummy/' "$ASDF_DIR/plugins/mummy/bin/install" run asdf install mummy 3.0 run asdf install dummy 1.0 mkdir "$PROJECT_DIR"/{A,B} echo "dummy 1.0" >"$PROJECT_DIR/A/.tool-versions" echo "mummy 3.0" >"$PROJECT_DIR/B/.tool-versions" cd "$PROJECT_DIR"/A run "$ASDF_DIR/shims/dummy" world hello [ "$output" = "This is Dummy 1.0! hello world" ] [ "$status" -eq 0 ] cd "$PROJECT_DIR"/B run "$ASDF_DIR/shims/dummy" world hello [ "$output" = "This is Mummy 3.0! hello world" ] [ "$status" -eq 0 ] } @test "shim exec should determine correct executable on a project with two plugins set that provide the same tool" { # Another fake plugin with 'dummy' executable cp -rf "$ASDF_DIR/plugins/dummy" "$ASDF_DIR/plugins/mummy" sed -i -e 's/Dummy/Mummy/' "$ASDF_DIR/plugins/mummy/bin/install" run asdf install dummy 1.0 run asdf install mummy 3.0 echo "dummy 2.0.0" >"$PROJECT_DIR/.tool-versions" echo "mummy 3.0" >>"$PROJECT_DIR/.tool-versions" echo "dummy 1.0" >>"$PROJECT_DIR/.tool-versions" run "$ASDF_DIR/shims/dummy" world hello [ "$output" = "This is Mummy 3.0! hello world" ] [ "$status" -eq 0 ] } @test "shim exec should fallback to system executable when specified version is system" { run asdf install dummy 1.0 echo "dummy system" >"$PROJECT_DIR/.tool-versions" mkdir "$PROJECT_DIR/foo/" echo "#!/usr/bin/env bash echo System" >"$PROJECT_DIR/foo/dummy" chmod +x "$PROJECT_DIR/foo/dummy" run env "PATH=$PATH:$PROJECT_DIR/foo" "$ASDF_DIR/shims/dummy" hello [ "$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 CUSTOM_DUMMY_PATH="$PROJECT_DIR/foo" CUSTOM_DUMMY_BIN_PATH="$CUSTOM_DUMMY_PATH/bin" mkdir -p "$CUSTOM_DUMMY_BIN_PATH" echo "#!/usr/bin/env bash echo System" >"$CUSTOM_DUMMY_BIN_PATH/dummy" chmod +x "$CUSTOM_DUMMY_BIN_PATH/dummy" echo "dummy path:$CUSTOM_DUMMY_PATH" >"$PROJECT_DIR/.tool-versions" run "$ASDF_DIR/shims/dummy" hello [ "$output" = "System" ] } @test "shim exec should execute system if set first" { run asdf install dummy 2.0.0 echo "dummy system" >"$PROJECT_DIR/.tool-versions" echo "dummy 2.0.0" >>"$PROJECT_DIR/.tool-versions" mkdir "$PROJECT_DIR/foo/" echo "#!/usr/bin/env bash echo System" >"$PROJECT_DIR/foo/dummy" chmod +x "$PROJECT_DIR/foo/dummy" run env "PATH=$PATH:$PROJECT_DIR/foo" "$ASDF_DIR/shims/dummy" hello [ "$output" = "System" ] } # These tests are disabled because the custom shims templates feature is no # longer supported. # #@test "shim exec should use custom exec-env for tool" { # run asdf install dummy 2.0.0 # echo '#!/usr/bin/env bash # export FOO=sourced' >"$ASDF_DIR/plugins/dummy/bin/exec-env" # mkdir "$ASDF_DIR/plugins/dummy/shims" # echo '#!/usr/bin/env bash # echo $FOO custom' >"$ASDF_DIR/plugins/dummy/shims/foo" # chmod +x "$ASDF_DIR/plugins/dummy/shims/foo" # run asdf reshim dummy 2.0.0 # echo "dummy 2.0.0" >"$PROJECT_DIR/.tool-versions" # run "$ASDF_DIR/shims/foo" # [ "$output" = "sourced custom" ] #} #@test "shim exec with custom exec-env using ASDF_INSTALL_PATH" { # run asdf install dummy 2.0.0 # echo 'export FOO=$ASDF_INSTALL_PATH/foo' >"$ASDF_DIR/plugins/dummy/bin/exec-env" # mkdir "$ASDF_DIR/plugins/dummy/shims" # echo 'echo $FOO custom' >"$ASDF_DIR/plugins/dummy/shims/foo" # chmod +x "$ASDF_DIR/plugins/dummy/shims/foo" # run asdf reshim dummy 2.0.0 # echo "dummy 2.0.0" >"$PROJECT_DIR/.tool-versions" # run "$ASDF_DIR/shims/foo" # [ "$output" = "$ASDF_DIR/installs/dummy/2.0.0/foo custom" ] #} #@test "shim exec doesn't not use custom exec-env for system version" { # run asdf install dummy 2.0.0 # echo "export FOO=sourced" >"$ASDF_DIR/plugins/dummy/bin/exec-env" # mkdir "$ASDF_DIR/plugins/dummy/shims" # echo 'echo $FOO custom' >"$ASDF_DIR/plugins/dummy/shims/foo" # chmod +x "$ASDF_DIR/plugins/dummy/shims/foo" # run asdf reshim dummy 2.0.0 # echo "dummy system" >"$PROJECT_DIR/.tool-versions" # mkdir "$PROJECT_DIR/sys/" # echo 'echo x$FOO System' >"$PROJECT_DIR/sys/foo" # chmod +x "$PROJECT_DIR/sys/foo" # run env "PATH=$PATH:$PROJECT_DIR/sys" "$ASDF_DIR/shims/foo" # [ "$output" = "x System" ] #} #@test "shim exec should prepend the plugin paths on execution" { # run asdf install dummy 2.0.0 # mkdir "$ASDF_DIR/plugins/dummy/shims" # echo 'which dummy' >"$ASDF_DIR/plugins/dummy/shims/foo" # chmod +x "$ASDF_DIR/plugins/dummy/shims/foo" # run asdf reshim dummy 2.0.0 # echo "dummy 2.0.0" >"$PROJECT_DIR/.tool-versions" # run "$ASDF_DIR/shims/foo" # [ "$output" = "$ASDF_DIR/installs/dummy/2.0.0/bin/dummy" ] #} #@test "shim exec should be able to find other shims in path" { # cp -rf "$ASDF_DIR/plugins/dummy" "$ASDF_DIR/plugins/gummy" # echo "dummy 2.0.0" >"$PROJECT_DIR/.tool-versions" # echo "gummy 2.0.0" >>"$PROJECT_DIR/.tool-versions" # run asdf install # mkdir "$ASDF_DIR/plugins/"{dummy,gummy}/shims # echo 'which dummy' >"$ASDF_DIR/plugins/dummy/shims/foo" # chmod +x "$ASDF_DIR/plugins/dummy/shims/foo" # echo 'which gummy' >"$ASDF_DIR/plugins/dummy/shims/bar" # chmod +x "$ASDF_DIR/plugins/dummy/shims/bar" # touch "$ASDF_DIR/plugins/gummy/shims/gummy" # chmod +x "$ASDF_DIR/plugins/gummy/shims/gummy" # run asdf reshim # run "$ASDF_DIR/shims/foo" # [ "$output" = "$ASDF_DIR/installs/dummy/2.0.0/bin/dummy" ] # run "$ASDF_DIR/shims/bar" # [ "$output" = "$ASDF_DIR/shims/gummy" ] #} @test "shim exec should remove shim_path from path on system version execution" { run asdf install dummy 2.0.0 echo "dummy system" >"$PROJECT_DIR/.tool-versions" mkdir "$PROJECT_DIR/sys/" echo '#!/usr/bin/env bash which dummy' >"$PROJECT_DIR/sys/dummy" chmod +x "$PROJECT_DIR/sys/dummy" run env "PATH=$PATH:$PROJECT_DIR/sys" "$ASDF_DIR/shims/dummy" [ "$output" = "$ASDF_DIR/shims/dummy" ] } @test "shim exec can take version from legacy file if configured" { run asdf install dummy 2.0.0 echo "legacy_version_file = yes" >"$HOME/.asdfrc" echo "2.0.0" >"$PROJECT_DIR/.dummy-version" run "$ASDF_DIR/shims/dummy" world hello [ "$output" = "This is Dummy 2.0.0! hello world" ] } @test "shim exec can take version from environment variable" { run asdf install dummy 2.0.0 run env ASDF_DUMMY_VERSION=2.0.0 "$ASDF_DIR/shims/dummy" world hello [ "$output" = "This is Dummy 2.0.0! hello world" ] } @test "shim exec uses plugin list-bin-paths" { exec_path="$ASDF_DIR/plugins/dummy/bin/list-bin-paths" custom_path="$ASDF_DIR/installs/dummy/1.0/custom" echo "echo bin custom" >"$exec_path" chmod +x "$exec_path" run asdf install dummy 1.0 echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions" mkdir "$custom_path" echo '#!/usr/bin/env bash echo CUSTOM' >"$custom_path/foo" chmod +x "$custom_path/foo" run asdf reshim dummy 1.0 run "$ASDF_DIR/shims/foo" [ "$output" = "CUSTOM" ] } @test "shim exec uses plugin custom exec-path hook" { run asdf install dummy 1.0 exec_path="$ASDF_DIR/plugins/dummy/bin/exec-path" custom_dummy="$ASDF_DIR/installs/dummy/1.0/custom/dummy" echo '#!/usr/bin/env bash echo custom/dummy' >"$exec_path" chmod +x "$exec_path" mkdir "$(dirname "$custom_dummy")" echo '#!/usr/bin/env bash echo CUSTOM' >"$custom_dummy" chmod +x "$custom_dummy" echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions" run "$ASDF_DIR/shims/dummy" [ "$output" = "CUSTOM" ] } @test "shim exec uses plugin custom exec-path hook that defaults" { run asdf install dummy 1.0 exec_path="$ASDF_DIR/plugins/dummy/bin/exec-path" echo '#!/usr/bin/env bash echo "$3" # always same path' >"$exec_path" chmod +x "$exec_path" echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions" run "$ASDF_DIR/shims/dummy" [ "$output" = "This is Dummy 1.0!" ] } @test "shim exec executes configured pre-hook" { run asdf install dummy 1.0 echo dummy 1.0 >"$PROJECT_DIR/.tool-versions" cat >"$HOME/.asdfrc" <<-'EOM' pre_dummy_dummy = echo PRE $1 $2 EOM run "$ASDF_DIR/shims/dummy" hello world [ "$status" -eq 0 ] echo "$output" | grep "PRE hello world" echo "$output" | grep "This is Dummy 1.0! world hello" } @test "shim exec doesn't execute command if pre-hook failed" { run asdf install dummy 1.0 echo dummy 1.0 >"$PROJECT_DIR/.tool-versions" mkdir "$HOME/hook" pre_cmd="$HOME/hook/pre" echo '#!/usr/bin/env bash echo $* && false' >"$pre_cmd" chmod +x "$pre_cmd" cat >"$HOME/.asdfrc" <<'EOM' pre_dummy_dummy = pre $1 $2 EOM run env PATH="$PATH:$HOME/hook" "$ASDF_DIR/shims/dummy" hello world [ "$output" = "hello world" ] [ "$status" -eq 1 ] } # From @tejanium in https://github.com/asdf-vm/asdf/issues/581#issuecomment-635337727 @test "asdf exec should not crash when POSIXLY_CORRECT=1" { export POSIXLY_CORRECT=1 echo "dummy 1.0" >"$PROJECT_DIR/.tool-versions" run asdf install run asdf exec dummy world hello [ "$output" = "This is Dummy 1.0! hello world" ] [ "$status" -eq 0 ] } ================================================ FILE: test/shim_versions_command.bats ================================================ #!/usr/bin/env bats # shellcheck disable=SC2164 load test_helpers setup() { setup_asdf_dir install_dummy_plugin PROJECT_DIR="$HOME/project" mkdir -p "$PROJECT_DIR" cd "$PROJECT_DIR" } teardown() { clean_asdf_dir } @test "shim_versions_command should list plugins and versions where command is available" { cd "$PROJECT_DIR" run asdf install dummy 3.0 run asdf install dummy 1.0 run asdf reshim dummy run asdf shimversions dummy [ "$status" -eq 0 ] echo "$output" | grep "dummy 3.0" echo "$output" | grep "dummy 1.0" } ================================================ FILE: test/test_helpers.bash ================================================ #!/usr/bin/env bash bats_require_minimum_version 1.7.0 # shellcheck source=lib/utils.bash . "$(dirname "$BATS_TEST_DIRNAME")"/lib/utils.bash setup_asdf_dir() { if [ "$BATS_TEST_NAME" = 'test_shim_exec_should_use_path_executable_when_specified_version_path-3a-3cpath-3e' ]; then BASE_DIR="$BASE_DIR/asdf_with_no_spaces" else BASE_DIR="$BASE_DIR/w space${BATS_TEST_NAME}" fi # We don't call mktemp anymore so we need to create this sub directory manually mkdir "$BASE_DIR" # HOME is now defined by the Golang test code in main_test.go HOME="$BASE_DIR" export HOME ASDF_DIR="$HOME/.asdf" mkdir -p "$ASDF_DIR/plugins" mkdir -p "$ASDF_DIR/installs" mkdir -p "$ASDF_DIR/shims" mkdir -p "$ASDF_DIR/tmp" # ASDF_BIN is now defined by the Golang test code in main_test.go #ASDF_BIN="$(dirname "$BATS_TEST_DIRNAME")/bin" ASDF_DATA_DIR="$BASE_DIR/.asdf" export ASDF_DATA_DIR # shellcheck disable=SC2031,SC2153 PATH="$ASDF_BIN:$ASDF_DIR/shims:$PATH" } install_mock_plugin() { local plugin_name=$1 local location="${2:-$ASDF_DIR}" plugin_dir="$location/plugins/$plugin_name" cp -r "$BATS_TEST_DIRNAME/fixtures/dummy_plugin" "$plugin_dir" init_git_repo "$plugin_dir" } install_mock_plugin_no_download() { local plugin_name=$1 local location="${2:-$ASDF_DIR}" cp -r "$BATS_TEST_DIRNAME/fixtures/dummy_plugin_no_download" "$location/plugins/$plugin_name" } install_mock_legacy_plugin() { local plugin_name=$1 local location="${2:-$ASDF_DIR}" plugin_dir="$location/plugins/$plugin_name" cp -r "$BATS_TEST_DIRNAME/fixtures/dummy_legacy_plugin" "$plugin_dir" init_git_repo "$plugin_dir" } install_mock_broken_plugin() { local plugin_name=$1 local location="${2:-$ASDF_DIR}" cp -r "$BATS_TEST_DIRNAME/fixtures/dummy_broken_plugin" "$location/plugins/$plugin_name" } install_mock_plugin_repo() { local plugin_name=$1 local location="${BASE_DIR}/repo-${plugin_name}" cp -r "$BATS_TEST_DIRNAME/fixtures/dummy_plugin" "${location}" init_git_repo "${location}" } init_git_repo() { location="$1" remote="${2:-"https://asdf-vm.com/fake-repo"}" git -C "${location}" init -q --initial-branch=master git -C "${location}" config user.name "Test" git -C "${location}" config user.email "test@example.com" git -C "${location}" add -A git -C "${location}" commit -q -m "asdf ${plugin_name} plugin" git -C "${location}" remote add origin "$remote" } install_mock_plugin_version() { local plugin_name=$1 local plugin_version=$2 local location="${3:-$ASDF_DIR}" mkdir -p "$location/installs/$plugin_name/$plugin_version" } install_dummy_plugin() { install_mock_plugin "dummy" } install_dummy_plugin_no_download() { install_mock_plugin_no_download "dummy-no-download" "$1" } install_dummy_legacy_plugin() { install_mock_legacy_plugin "legacy-dummy" } install_dummy_broken_plugin() { install_mock_broken_plugin "dummy-broken" } install_dummy_version() { install_mock_plugin_version "dummy" "$1" } install_dummy_legacy_version() { install_mock_plugin_version "legacy-dummy" "$1" } install_dummy_exec_path_script() { local name=$1 local exec_path="$ASDF_DIR/plugins/dummy/bin/exec-path" local custom_dir="$ASDF_DIR/installs/dummy/1.0/bin/custom" mkdir "$custom_dir" touch "$custom_dir/$name" chmod +x "$custom_dir/$name" echo "echo 'bin/custom/$name'" >"$exec_path" chmod +x "$exec_path" } clean_asdf_dir() { rm -rf "$BASE_DIR" unset ASDF_DIR unset ASDF_DATA_DIR } setup_repo() { cp -r "$BATS_TEST_DIRNAME/fixtures/dummy_plugins_repo" "$ASDF_DIR/plugin-index" cp -r "$BATS_TEST_DIRNAME/fixtures/dummy_plugins_repo" "$ASDF_DIR/plugin-index-2" init_git_repo "$ASDF_DIR/plugin-index-2" init_git_repo "$ASDF_DIR/plugin-index" "$ASDF_DIR/plugin-index-2" touch "$(asdf_dir)/tmp/repo-updated" } ================================================ FILE: test/uninstall_command.bats ================================================ #!/usr/bin/env bats load test_helpers setup() { setup_asdf_dir install_dummy_plugin PROJECT_DIR="$HOME/project" mkdir -p "$PROJECT_DIR" } teardown() { clean_asdf_dir } @test "uninstall_command should fail when no such version is installed" { run asdf uninstall dummy 3.14 [ "$output" = "No such version" ] [ "$status" -eq 1 ] } @test "uninstall_command should remove the plugin with that version from asdf" { run asdf install dummy 1.1.0 [ "$status" -eq 0 ] [ "$(cat "$ASDF_DIR/installs/dummy/1.1.0/version")" = "1.1.0" ] run asdf uninstall dummy 1.1.0 [ ! -f "$ASDF_DIR/installs/dummy/1.1.0/version" ] } @test "uninstall_command should invoke the plugin bin/uninstall if available" { run asdf install dummy 1.1.0 [ "$status" -eq 0 ] mkdir -p "$ASDF_DIR/plugins/dummy/bin" 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" ] [ "$status" -eq 0 ] } @test "uninstall_command should remove the plugin shims if no other version is installed" { run asdf install dummy 1.1.0 [ -f "$ASDF_DIR/shims/dummy" ] run asdf uninstall dummy 1.1.0 [ ! -f "$ASDF_DIR/shims/dummy" ] } @test "uninstall_command should leave the plugin shims if other version is installed" { run asdf install dummy 1.0.0 [ -f "$ASDF_DIR/installs/dummy/1.0.0/bin/dummy" ] run asdf install dummy 1.1.0 [ -f "$ASDF_DIR/installs/dummy/1.1.0/bin/dummy" ] [ -f "$ASDF_DIR/shims/dummy" ] run asdf uninstall dummy 1.0.0 [ -f "$ASDF_DIR/shims/dummy" ] } @test "uninstall_command should remove relevant asdf-plugin metadata" { run asdf install dummy 1.0.0 [ -f "$ASDF_DIR/installs/dummy/1.0.0/bin/dummy" ] run asdf install dummy 1.1.0 [ -f "$ASDF_DIR/installs/dummy/1.1.0/bin/dummy" ] run asdf uninstall dummy 1.0.0 run grep "asdf-plugin: dummy 1.1.0" "$ASDF_DIR/shims/dummy" [ "$status" -eq 0 ] run grep "asdf-plugin: dummy 1.0.0" "$ASDF_DIR/shims/dummy" [ "$status" -eq 1 ] } # Disabled as this test represents an invalid state. A shim (`gummy`) should # never exist unless it referenced an existing tool and version. # #@test "uninstall_command should not remove other unrelated shims" { # run asdf install dummy 1.0.0 # [ -f "$ASDF_DIR/shims/dummy" ] # touch "$ASDF_DIR/shims/gummy" # [ -f "$ASDF_DIR/shims/gummy" ] # run asdf uninstall dummy 1.0.0 # [ -f "$ASDF_DIR/shims/gummy" ] #} @test "uninstall command executes configured pre hook" { cat >"$HOME/.asdfrc" <<-'EOM' pre_asdf_uninstall_dummy = echo will uninstall dummy $1 EOM run asdf install dummy 1.0.0 run asdf uninstall dummy 1.0.0 [ "$output" = "will uninstall dummy 1.0.0" ] } @test "uninstall command executes configured post hook" { cat >"$HOME/.asdfrc" <<-'EOM' post_asdf_uninstall_dummy = echo removed dummy $1 EOM run asdf install dummy 1.0.0 run asdf uninstall dummy 1.0.0 [ "$output" = "removed dummy 1.0.0" ] } ================================================ FILE: test/update_command.bats ================================================ #!/usr/bin/env bats #load test_helpers #setup() { # BASE_DIR=$(mktemp -dt asdf.XXXX) # HOME="$BASE_DIR/home" # ASDF_DIR="$HOME/.asdf" # git clone -o local "$(dirname "$BATS_TEST_DIRNAME")" "$ASDF_DIR" # git --git-dir "$ASDF_DIR/.git" remote add origin https://github.com/asdf-vm/asdf.git # mkdir -p "$ASDF_DIR/plugins" # mkdir -p "$ASDF_DIR/installs" # mkdir -p "$ASDF_DIR/shims" # mkdir -p "$ASDF_DIR/tmp" # ASDF_BIN="$ASDF_DIR/bin" # # shellcheck disable=SC2031 # PATH="$ASDF_BIN:$ASDF_DIR/shims:$PATH" # install_dummy_plugin # PROJECT_DIR="$HOME/project" # mkdir -p "$PROJECT_DIR" #} #teardown() { # clean_asdf_dir #} #@test "asdf update --head should checkout the master branch" { # run asdf update --head # [ "$status" -eq 0 ] # cd "$ASDF_DIR" # [ "$(git rev-parse --abbrev-ref HEAD)" = "master" ] #} #@test "asdf update should checkout the latest non-RC tag" { # local tag= # tag=$(git tag | grep -vi "rc" | tail -1) # if [ -n "$tag" ]; then # run asdf update # [ "$status" -eq 0 ] # cd "$ASDF_DIR" # git tag | grep "$tag" # fi #} #@test "asdf update should checkout the latest tag when configured with use_release_candidates = yes" { # local tag= # tag=$(git tag | tail -1) # if [ -n "$tag" ]; then # export ASDF_CONFIG_DEFAULT_FILE="$BATS_TMPDIR/asdfrc_defaults" # echo "use_release_candidates = yes" >"$ASDF_CONFIG_DEFAULT_FILE" # run asdf update # [ "$status" -eq 0 ] # cd "$ASDF_DIR" # git tag | grep "$tag" # fi #} #@test "asdf update is a noop for when updates are disabled" { # touch "$ASDF_DIR/asdf_updates_disabled" # run asdf update # [ "$status" -eq 42 ] # [ $'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 ] # [ $'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" { # git --git-dir "$ASDF_DIR/.git" remote set-url origin https://this-host-does-not-exist.xyz # run asdf update # [ "$status" -eq 1 ] #} #@test "asdf update should not remove plugin versions" { # run asdf install dummy 1.1.0 # [ "$status" -eq 0 ] # [ "$(cat "$ASDF_DIR/installs/dummy/1.1.0/version")" = "1.1.0" ] # run asdf update # [ "$status" -eq 0 ] # [ -f "$ASDF_DIR/installs/dummy/1.1.0/version" ] # run asdf update --head # [ "$status" -eq 0 ] # [ -f "$ASDF_DIR/installs/dummy/1.1.0/version" ] #} #@test "asdf update should not remove plugins" { # # dummy plugin is already installed # run asdf update # [ "$status" -eq 0 ] # [ -d "$ASDF_DIR/plugins/dummy" ] # run asdf update --head # [ "$status" -eq 0 ] # [ -d "$ASDF_DIR/plugins/dummy" ] #} #@test "asdf update should not remove shims" { # run asdf install dummy 1.1.0 # [ -f "$ASDF_DIR/shims/dummy" ] # run asdf update # [ "$status" -eq 0 ] # [ -f "$ASDF_DIR/shims/dummy" ] # run asdf update --head # [ "$status" -eq 0 ] # [ -f "$ASDF_DIR/shims/dummy" ] #} ================================================ FILE: test/utils.bats ================================================ #!/usr/bin/env bats # shellcheck disable=SC2030,SC2031,SC2164 load test_helpers setup() { setup_asdf_dir install_dummy_plugin install_dummy_version "0.1.0" install_dummy_version "0.2.0" PROJECT_DIR="$HOME/project" mkdir -p "$PROJECT_DIR" cd "$HOME" } teardown() { clean_asdf_dir } @test "get_install_path should output version path when version is provided" { run get_install_path foo version "1.0.0" [ "$status" -eq 0 ] install_path=${output#"$HOME/"} [ "$install_path" = ".asdf/installs/foo/1.0.0" ] } @test "get_install_path should output custom path when custom install type is provided" { run get_install_path foo custom "1.0.0" [ "$status" -eq 0 ] install_path=${output#"$HOME/"} [ "$install_path" = ".asdf/installs/foo/custom-1.0.0" ] } @test "get_install_path should output path when path version is provided" { run get_install_path foo path "/some/path" [ "$status" -eq 0 ] [ "$output" = "/some/path" ] } @test "get_download_path should output version path when version is provided" { run get_download_path foo version "1.0.0" [ "$status" -eq 0 ] download_path=${output#"$HOME/"} [ "$download_path" = ".asdf/downloads/foo/1.0.0" ] } @test "get_download_path should output custom path when custom download type is provided" { run get_download_path foo custom "1.0.0" [ "$status" -eq 0 ] download_path=${output#"$HOME/"} [ "$download_path" = ".asdf/downloads/foo/custom-1.0.0" ] } @test "get_download_path should output nothing when path version is provided" { run get_download_path foo path "/some/path" [ "$status" -eq 0 ] [ "$output" = "" ] } @test "check_if_version_exists should exit with 1 if plugin does not exist" { run check_if_version_exists "inexistent" "1.0.0" [ "$status" -eq 1 ] [ "$output" = "No such plugin: inexistent" ] } @test "check_if_version_exists should exit with 1 if version does not exist" { run check_if_version_exists "dummy" "1.0.0" [ "$status" -eq 1 ] } @test "version_not_installed_text is correct" { run version_not_installed_text "dummy" "1.0.0" [ "$status" -eq 0 ] [ "$output" = "version 1.0.0 is not installed for dummy" ] } @test "check_if_version_exists should be noop if version exists" { run check_if_version_exists "dummy" "0.1.0" [ "$status" -eq 0 ] [ "$output" = "" ] } @test "check_if_version_exists should be noop if version is system" { mkdir -p "$ASDF_DIR/plugins/foo" run check_if_version_exists "foo" "system" [ "$status" -eq 0 ] [ "$output" = "" ] } @test "check_if_version_exists should be ok for ref:version install" { mkdir -p "$ASDF_DIR/plugins/foo" mkdir -p "$ASDF_DIR/installs/foo/ref-master" run check_if_version_exists "foo" "ref:master" [ "$status" -eq 0 ] [ "$output" = "" ] } @test "check_if_plugin_exists should exit with 1 when plugin is empty string" { run check_if_plugin_exists [ "$status" -eq 1 ] [ "$output" = "No plugin given" ] } @test "check_if_plugin_exists should be noop if plugin exists" { run check_if_plugin_exists "dummy" [ "$status" -eq 0 ] [ "$output" = "" ] } @test "parse_asdf_version_file should output version" { echo "dummy 0.1.0" >"$PROJECT_DIR/.tool-versions" run parse_asdf_version_file "$PROJECT_DIR/.tool-versions" dummy [ "$status" -eq 0 ] [ "$output" = "0.1.0" ] } @test "parse_asdf_version_file should output path on project with spaces" { PROJECT_DIR="$PROJECT_DIR/outer space" mkdir -p "$PROJECT_DIR" echo "dummy 0.1.0" >"$PROJECT_DIR/.tool-versions" run parse_asdf_version_file "$PROJECT_DIR/.tool-versions" dummy [ "$status" -eq 0 ] [ "$output" = "0.1.0" ] } @test "parse_asdf_version_file should output path version with spaces" { echo "dummy path:/some/dummy path" >"$PROJECT_DIR/.tool-versions" run parse_asdf_version_file "$PROJECT_DIR/.tool-versions" dummy [ "$status" -eq 0 ] [ "$output" = "path:/some/dummy path" ] } @test "parse_asdf_version_file should output path version with tilde" { echo "dummy path:~/some/dummy path" >"$PROJECT_DIR/.tool-versions" run parse_asdf_version_file "$PROJECT_DIR/.tool-versions" dummy [ "$status" -eq 0 ] [ "$output" = "path:$HOME/some/dummy path" ] } @test "find_versions should return .tool-versions if legacy is disabled" { echo "dummy 0.1.0" >"$PROJECT_DIR/.tool-versions" echo "0.2.0" >"$PROJECT_DIR/.dummy-version" run find_versions "dummy" "$PROJECT_DIR" [ "$status" -eq 0 ] [ "$output" = "0.1.0|$PROJECT_DIR/.tool-versions" ] } @test "find_versions should return the legacy file if supported" { echo "legacy_version_file = yes" >"$HOME/.asdfrc" echo "dummy 0.1.0" >"$HOME/.tool-versions" echo "0.2.0" >"$PROJECT_DIR/.dummy-version" run find_versions "dummy" "$PROJECT_DIR" [ "$status" -eq 0 ] [ "$output" = "0.2.0|$PROJECT_DIR/.dummy-version" ] } @test "find_versions skips .tool-version file that don't list the plugin" { echo "dummy 0.1.0" >"$HOME/.tool-versions" echo "another_plugin 0.3.0" >"$PROJECT_DIR/.tool-versions" run find_versions "dummy" "$PROJECT_DIR" [ "$status" -eq 0 ] [ "$output" = "0.1.0|$HOME/.tool-versions" ] } @test "find_versions should return .tool-versions if unsupported" { echo "dummy 0.1.0" >"$HOME/.tool-versions" echo "0.2.0" >"$PROJECT_DIR/.dummy-version" echo "legacy_version_file = yes" >"$HOME/.asdfrc" rm "$ASDF_DIR/plugins/dummy/bin/list-legacy-filenames" run find_versions "dummy" "$PROJECT_DIR" [ "$status" -eq 0 ] [ "$output" = "0.1.0|$HOME/.tool-versions" ] } @test "find_versions should return the version set by environment variable" { export ASDF_DUMMY_VERSION=0.2.0 run find_versions "dummy" "$PROJECT_DIR" [ "$status" -eq 0 ] [ "$output" = "0.2.0|ASDF_DUMMY_VERSION environment variable" ] } @test "asdf_data_dir should return user dir if configured" { ASDF_DATA_DIR="/tmp/wadus" run asdf_data_dir [ "$status" -eq 0 ] [ "$output" = "$ASDF_DATA_DIR" ] } @test "asdf_data_dir should return ~/.asdf when ASDF_DATA_DIR is not set" { unset ASDF_DATA_DIR run asdf_data_dir [ "$status" -eq 0 ] [ "$output" = "$HOME/.asdf" ] } @test "check_if_plugin_exists should work with a custom data directory" { ASDF_DATA_DIR=$HOME/asdf-data mkdir -p "$ASDF_DATA_DIR/plugins" mkdir -p "$ASDF_DATA_DIR/installs" install_mock_plugin "dummy2" "$ASDF_DATA_DIR" install_mock_plugin_version "dummy2" "0.1.0" "$ASDF_DATA_DIR" run check_if_plugin_exists "dummy2" [ "$status" -eq 0 ] [ "$output" = "" ] } @test "find_versions should return \$ASDF_TOOL_VERSIONS_FILENAME if set" { ASDF_TOOL_VERSIONS_FILENAME="$PROJECT_DIR/global-tool-versions" echo "dummy 0.1.0" >"$ASDF_TOOL_VERSIONS_FILENAME" run find_versions "dummy" "$PROJECT_DIR" [ "$status" -eq 0 ] [ "$output" = "0.1.0|$ASDF_TOOL_VERSIONS_FILENAME" ] } @test "find_versions should check \$HOME legacy files before \$ASDF_TOOL_VERSIONS_FILENAME" { ASDF_TOOL_VERSIONS_FILENAME="$PROJECT_DIR/global-tool-versions" echo "dummy 0.2.0" >"$ASDF_TOOL_VERSIONS_FILENAME" echo "dummy 0.1.0" >"$HOME/.dummy-version" echo "legacy_version_file = yes" >"$HOME/.asdfrc" run find_versions "dummy" "$PROJECT_DIR" [ "$status" -eq 0 ] [[ "$output" == *"0.1.0|$HOME/.dummy-version"* ]] } @test "get_preset_version_for returns the current version" { cd "$PROJECT_DIR" echo "dummy 0.2.0" >.tool-versions run get_preset_version_for "dummy" [ "$status" -eq 0 ] [ "$output" = "0.2.0" ] } @test "get_preset_version_for returns the global version from home when project is outside of home" { echo "dummy 0.1.0" >"$HOME/.tool-versions" PROJECT_DIR=$BASE_DIR/project mkdir -p "$PROJECT_DIR" run get_preset_version_for "dummy" [ "$status" -eq 0 ] [ "$output" = "0.1.0" ] } @test "get_preset_version_for returns the tool version from env if ASDF_{TOOL}_VERSION is defined" { cd "$PROJECT_DIR" echo "dummy 0.2.0" >.tool-versions ASDF_DUMMY_VERSION=3.0.0 run get_preset_version_for "dummy" [ "$status" -eq 0 ] [ "$output" = "3.0.0" ] } @test "get_preset_version_for should return branch reference version" { cd "$PROJECT_DIR" echo "dummy ref:master" >"$PROJECT_DIR/.tool-versions" run get_preset_version_for "dummy" [ "$status" -eq 0 ] [ "$output" = "ref:master" ] } @test "get_preset_version_for should return path version" { cd "$PROJECT_DIR" echo "dummy path:/some/place with spaces" >"$PROJECT_DIR/.tool-versions" run get_preset_version_for "dummy" [ "$status" -eq 0 ] [ "$output" = "path:/some/place with spaces" ] } @test "get_preset_version_for should return path version with tilde" { cd "$PROJECT_DIR" echo "dummy path:~/some/place with spaces" >"$PROJECT_DIR/.tool-versions" run get_preset_version_for "dummy" [ "$status" -eq 0 ] [ "$output" = "path:$HOME/some/place with spaces" ] } @test "get_executable_path for system version should return system path" { mkdir -p "$ASDF_DIR/plugins/foo" run get_executable_path "foo" "system" "ls" [ "$status" -eq 0 ] [ "$output" = "$(which ls)" ] } @test "get_executable_path for system version should not use asdf shims" { mkdir -p "$ASDF_DIR/plugins/foo" touch "$ASDF_DIR/shims/dummy_executable" chmod +x "$ASDF_DIR/shims/dummy_executable" run which dummy_executable [ "$status" -eq 0 ] run get_executable_path "foo" "system" "dummy_executable" [ "$status" -eq 1 ] } @test "get_executable_path for non system version should return relative path from plugin" { mkdir -p "$ASDF_DIR/plugins/foo" mkdir -p "$ASDF_DIR/installs/foo/1.0.0/bin" executable_path="$ASDF_DIR/installs/foo/1.0.0/bin/dummy" touch "$executable_path" chmod +x "$executable_path" run get_executable_path "foo" "1.0.0" "bin/dummy" [ "$status" -eq 0 ] [ "$output" = "$executable_path" ] } @test "get_executable_path for ref:version installed version should resolve to ref-version" { mkdir -p "$ASDF_DIR/plugins/foo" mkdir -p "$ASDF_DIR/installs/foo/ref-master/bin" executable_path="$ASDF_DIR/installs/foo/ref-master/bin/dummy" touch "$executable_path" chmod +x "$executable_path" run get_executable_path "foo" "ref:master" "bin/dummy" [ "$status" -eq 0 ] [ "$output" = "$executable_path" ] } @test "find_tool_versions will find a .tool-versions path if it exists in current directory" { echo "dummy 0.1.0" >"$PROJECT_DIR/.tool-versions" cd "$PROJECT_DIR" run find_tool_versions [ "$status" -eq 0 ] [ "$output" = "$PROJECT_DIR/.tool-versions" ] } @test "find_tool_versions will find a .tool-versions path if it exists in parent directory" { echo "dummy 0.1.0" >"$PROJECT_DIR/.tool-versions" mkdir -p "$PROJECT_DIR/child" cd "$PROJECT_DIR"/child run find_tool_versions [ "$status" -eq 0 ] [ "$output" = "$PROJECT_DIR/.tool-versions" ] } @test "get_version_from_env returns the version set in the environment variable" { export ASDF_DUMMY_VERSION=0.1.0 run get_version_from_env 'dummy' [ "$status" -eq 0 ] [ "$output" = '0.1.0' ] } @test "get_version_from_env returns nothing when environment variable is not set" { run get_version_from_env 'dummy' [ "$status" -eq 0 ] [ "$output" = '' ] } @test "resolve_symlink converts the symlink path to the real file path" { touch foo ln -s "$PWD/foo" bar run resolve_symlink bar [ "$status" -eq 0 ] [ "$output" = "$PWD/foo" ] rm -f foo bar } @test "resolve_symlink converts relative symlink directory path to the real file path" { mkdir baz ln -s ../foo baz/bar run resolve_symlink baz/bar [ "$status" -eq 0 ] [ "$output" = "$PWD/baz/../foo" ] rm -f foo bar } @test "resolve_symlink converts relative symlink path to the real file path" { touch foo ln -s foo bar run resolve_symlink bar [ "$status" -eq 0 ] [ "$output" = "$PWD/foo" ] rm -f foo bar } @test "strip_tool_version_comments removes lines that only contain comments" { cat <<EOF >test_file # comment line ruby 2.0.0 EOF run strip_tool_version_comments test_file [ "$status" -eq 0 ] [ "$output" = "ruby 2.0.0" ] } @test "strip_tool_version_comments removes lines that only contain comments even with missing newline" { echo -n "# comment line" >test_file run strip_tool_version_comments test_file [ "$status" -eq 0 ] [ "$output" = "" ] } @test "strip_tool_version_comments removes trailing comments on lines containing version information" { cat <<EOF >test_file ruby 2.0.0 # inline comment EOF run strip_tool_version_comments test_file [ "$status" -eq 0 ] [ "$output" = "ruby 2.0.0" ] } @test "strip_tool_version_comments removes trailing comments on lines containing version information even with missing newline" { echo -n "ruby 2.0.0 # inline comment" >test_file run strip_tool_version_comments test_file [ "$status" -eq 0 ] [ "$output" = "ruby 2.0.0" ] } @test "strip_tool_version_comments removes all comments from the version file" { cat <<EOF >test_file ruby 2.0.0 # inline comment # comment line erlang 18.2.1 # inline comment EOF expected="$( cat <<EOF ruby 2.0.0 erlang 18.2.1 EOF )" run strip_tool_version_comments test_file [ "$status" -eq 0 ] [ "$output" = "$expected" ] } @test "with_shim_executable doesn't crash when executable names contain dashes" { cd "$PROJECT_DIR" echo "dummy 0.1.0" >"$PROJECT_DIR/.tool-versions" mkdir -p "$ASDF_DIR/installs/dummy/0.1.0/bin" touch "$ASDF_DIR/installs/dummy/0.1.0/bin/test-dash" chmod +x "$ASDF_DIR/installs/dummy/0.1.0/bin/test-dash" run asdf reshim dummy 0.1.0 message="callback invoked" callback() { echo "$message" } run with_shim_executable test-dash callback [ "$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"* ]] } ================================================ FILE: test/version_commands.bats ================================================ #!/usr/bin/env bats # shellcheck disable=SC2012,SC2030,SC2031,SC2164 load test_helpers setup() { setup_asdf_dir install_dummy_plugin install_dummy_version "1.0.0" install_dummy_version "1.1.0" install_dummy_version "2.0.0" install_dummy_legacy_plugin install_dummy_legacy_version "1.0.0" install_dummy_legacy_version "1.1.0" install_dummy_legacy_version "2.0.0" install_dummy_legacy_version "5.1.0" PROJECT_DIR="$HOME/project" mkdir -p "$PROJECT_DIR" CHILD_DIR="$PROJECT_DIR/child-dir" mkdir -p "$CHILD_DIR" cd "$PROJECT_DIR" # asdf lib needed to run asdf.sh cp -rf "$BATS_TEST_DIRNAME"/../{bin,lib} "$ASDF_DIR/" } teardown() { clean_asdf_dir } # Warn users who invoke the old style command without arguments. @test "local should emit an error when called with incorrect arity" { run asdf local "dummy" [ "$status" -eq 1 ] [ "$output" = "Usage: asdf local <name> <version>" ] } @test "local should emit an error when plugin does not exist" { run asdf local "inexistent" "1.0.0" [ "$status" -eq 1 ] [ "$output" = "No such plugin: inexistent" ] } @test "local should emit an error when plugin version does not exist" { run asdf local "dummy" "0.0.1" [ "$status" -eq 1 ] [ "$output" = "version 0.0.1 is not installed for dummy" ] } @test "local should create a local .tool-versions file if it doesn't exist" { run asdf local "dummy" "1.1.0" [ "$status" -eq 0 ] [ "$(cat "$PROJECT_DIR/.tool-versions")" = "dummy 1.1.0" ] } @test "[local - dummy_plugin] with latest should use the latest installed version" { run asdf local "dummy" "latest" [ "$status" -eq 0 ] [ "$(cat "$PROJECT_DIR/.tool-versions")" = "dummy 2.0.0" ] } @test "[local - dummy_plugin] with latest:version should use the latest valid installed version" { run asdf local "dummy" "latest:1.0" [ "$status" -eq 0 ] [ "$(cat "$PROJECT_DIR/.tool-versions")" = "dummy 1.0.0" ] } @test "[local - dummy_plugin] with latest:version should return an error for invalid versions" { run asdf local "dummy" "latest:99" [ "$status" -eq 1 ] [ "$output" = "No compatible versions available (dummy 99)" ] } @test "[local - dummy_legacy_plugin] with latest should use the latest installed version" { run asdf local "legacy-dummy" "latest" [ "$status" -eq 0 ] [ "$(cat "$PROJECT_DIR/.tool-versions")" = "legacy-dummy 5.1.0" ] } @test "[local - dummy_legacy_plugin] with latest:version should use the latest valid installed version" { run asdf local "legacy-dummy" "latest:1.0" [ "$status" -eq 0 ] [ "$(cat "$PROJECT_DIR/.tool-versions")" = "legacy-dummy 1.0.0" ] } @test "[local - dummy_legacy_plugin] with latest:version should return an error for invalid versions" { run asdf local "legacy-dummy" "latest:99" [ "$status" -eq 1 ] [ "$output" = "No compatible versions available (legacy-dummy 99)" ] } @test "local should allow multiple versions" { run asdf local "dummy" "1.1.0" "1.0.0" [ "$status" -eq 0 ] [ "$(cat "$PROJECT_DIR/.tool-versions")" = "dummy 1.1.0 1.0.0" ] } @test "local should create a local .tool-versions file if it doesn't exist when the directory name contains whitespace" { WHITESPACE_DIR="$PROJECT_DIR/whitespace\ dir" mkdir -p "$WHITESPACE_DIR" cd "$WHITESPACE_DIR" run asdf local "dummy" "1.1.0" tool_version_contents=$(cat "$WHITESPACE_DIR/.tool-versions") [ "$status" -eq 0 ] [ "$tool_version_contents" = "dummy 1.1.0" ] } @test "local should not create a duplicate .tool-versions file if such file exists" { echo 'dummy 1.0.0' >>"$PROJECT_DIR/.tool-versions" run asdf local "dummy" "1.1.0" [ "$status" -eq 0 ] [ "$(ls "$PROJECT_DIR/.tool-versions"* | wc -l)" -eq 1 ] } @test "local should overwrite the existing version if it's set" { echo 'dummy 1.0.0' >>"$PROJECT_DIR/.tool-versions" run asdf local "dummy" "1.1.0" [ "$status" -eq 0 ] [ "$(cat "$PROJECT_DIR/.tool-versions")" = "dummy 1.1.0" ] } @test "local should append trailing newline before appending new version when missing" { echo -n 'foobar 1.0.0' >>"$PROJECT_DIR/.tool-versions" run asdf local "dummy" "1.1.0" [ "$status" -eq 0 ] [ "$(cat "$PROJECT_DIR/.tool-versions")" = $'foobar 1.0.0\ndummy 1.1.0' ] } @test "local should not append trailing newline before appending new version when one present" { echo 'foobar 1.0.0' >>"$PROJECT_DIR/.tool-versions" run asdf local "dummy" "1.1.0" [ "$status" -eq 0 ] [ "$(cat "$PROJECT_DIR/.tool-versions")" = $'foobar 1.0.0\ndummy 1.1.0' ] } @test "local should fail to set a path:dir if dir does not exists " { run asdf local "dummy" "path:$PROJECT_DIR/local" [ "$output" = "version path:$PROJECT_DIR/local is not installed for dummy" ] [ "$status" -eq 1 ] } @test "local should set a path:dir if dir exists " { mkdir -p "$PROJECT_DIR/local" run asdf local "dummy" "path:$PROJECT_DIR/local" [ "$status" -eq 0 ] [ "$(cat "$PROJECT_DIR/.tool-versions")" = "dummy path:$PROJECT_DIR/local" ] } @test "local -p/--parent should set should emit an error when called with incorrect arity" { run asdf local -p "dummy" [ "$status" -eq 1 ] [ "$output" = "Usage: asdf local <name> <version>" ] } @test "local -p/--parent should emit an error when plugin does not exist" { run asdf local -p "inexistent" "1.0.0" [ "$status" -eq 1 ] [ "$output" = "No such plugin: inexistent" ] } @test "local -p/--parent should emit an error when plugin version does not exist" { run asdf local -p "dummy" "0.0.1" [ "$status" -eq 1 ] [ "$output" = "version 0.0.1 is not installed for dummy" ] } @test "local -p/--parent should allow multiple versions" { cd "$CHILD_DIR" touch "$PROJECT_DIR/.tool-versions" run asdf local -p "dummy" "1.1.0" "1.0.0" [ "$status" -eq 0 ] [ "$(cat "$PROJECT_DIR/.tool-versions")" = "dummy 1.1.0 1.0.0" ] } @test "local -p/--parent should overwrite the existing version if it's set" { cd "$CHILD_DIR" echo 'dummy 1.0.0' >>"$PROJECT_DIR/.tool-versions" run asdf local -p "dummy" "1.1.0" [ "$status" -eq 0 ] [ "$(cat "$PROJECT_DIR/.tool-versions")" = "dummy 1.1.0" ] } @test "local -p/--parent should set the version if it's unset" { cd "$CHILD_DIR" touch "$PROJECT_DIR/.tool-versions" run asdf local -p "dummy" "1.1.0" [ "$status" -eq 0 ] [ "$(cat "$PROJECT_DIR/.tool-versions")" = "dummy 1.1.0" ] } @test "global should create a global .tool-versions file if it doesn't exist" { run asdf global "dummy" "1.1.0" [ "$status" -eq 0 ] [ "$(cat "$HOME/.tool-versions")" = "dummy 1.1.0" ] } @test "[global - dummy_plugin] with latest should use the latest installed version" { run asdf global "dummy" "latest" [ "$status" -eq 0 ] [ "$(cat "$HOME/.tool-versions")" = "dummy 2.0.0" ] } @test "[global - dummy_plugin] with latest:version should use the latest valid installed version" { run asdf global "dummy" "latest:1.0" [ "$status" -eq 0 ] [ "$(cat "$HOME/.tool-versions")" = "dummy 1.0.0" ] } @test "[global - dummy_plugin] with latest:version should return an error for invalid versions" { run asdf global "dummy" "latest:99" [ "$status" -eq 1 ] [ "$output" = "No compatible versions available (dummy 99)" ] } @test "[global - dummy_legacy_plugin] with latest should use the latest installed version" { run asdf global "legacy-dummy" "latest" [ "$status" -eq 0 ] [ "$(cat "$HOME/.tool-versions")" = "legacy-dummy 5.1.0" ] } @test "[global - dummy_legacy_plugin] with latest:version should use the latest valid installed version" { run asdf global "legacy-dummy" "latest:1.0" [ "$status" -eq 0 ] [ "$(cat "$HOME/.tool-versions")" = "legacy-dummy 1.0.0" ] } @test "[global - dummy_legacy_plugin] with latest:version should return an error for invalid versions" { run asdf global "legacy-dummy" "latest:99" [ "$status" -eq 1 ] [ "$output" = "No compatible versions available (legacy-dummy 99)" ] } @test "global should accept multiple versions" { run asdf global "dummy" "1.1.0" "1.0.0" [ "$status" -eq 0 ] [ "$(cat "$HOME/.tool-versions")" = "dummy 1.1.0 1.0.0" ] } @test "global should overwrite the existing version if it's set" { echo 'dummy 1.0.0' >>"$HOME/.tool-versions" run asdf global "dummy" "1.1.0" [ "$status" -eq 0 ] [ "$(cat "$HOME/.tool-versions")" = "dummy 1.1.0" ] } @test "global should append trailing newline before appending new version when missing" { echo -n 'foobar 1.0.0' >>"$HOME/.tool-versions" run asdf global "dummy" "1.1.0" [ "$status" -eq 0 ] [ "$(cat "$HOME/.tool-versions")" = $'foobar 1.0.0\ndummy 1.1.0' ] } @test "global should not append trailing newline before appending new version when one present" { echo 'foobar 1.0.0' >>"$HOME/.tool-versions" run asdf global "dummy" "1.1.0" [ "$status" -eq 0 ] [ "$(cat "$HOME/.tool-versions")" = $'foobar 1.0.0\ndummy 1.1.0' ] } @test "global should fail to set a path:dir if dir does not exists " { run asdf global "dummy" "path:$PROJECT_DIR/local" [ "$output" = "version path:$PROJECT_DIR/local is not installed for dummy" ] [ "$status" -eq 1 ] } @test "global should set a path:dir if dir exists " { mkdir -p "$PROJECT_DIR/local" run asdf global "dummy" "path:$PROJECT_DIR/local" [ "$status" -eq 0 ] [ "$(cat "$HOME/.tool-versions")" = "dummy path:$PROJECT_DIR/local" ] } @test "local should write to ASDF_TOOL_VERSIONS_FILENAME" { export ASDF_TOOL_VERSIONS_FILENAME="local-tool-versions" run asdf local "dummy" "1.1.0" [ "$status" -eq 0 ] [ "$(cat "$ASDF_TOOL_VERSIONS_FILENAME")" = "dummy 1.1.0" ] [ -z "$(cat .tool-versions)" ] unset ASDF_TOOL_VERSIONS_FILENAME } @test "local should overwrite contents of ASDF_TOOL_VERSIONS_FILENAME if set" { export ASDF_TOOL_VERSIONS_FILENAME="local-tool-versions" echo 'dummy 1.0.0' >>"$ASDF_TOOL_VERSIONS_FILENAME" run asdf local "dummy" "1.1.0" [ "$status" -eq 0 ] [ "$(cat "$ASDF_TOOL_VERSIONS_FILENAME")" = "dummy 1.1.0" ] [ -z "$(cat .tool-versions)" ] unset ASDF_TOOL_VERSIONS_FILENAME } @test "global should write to ASDF_TOOL_VERSIONS_FILENAME" { export ASDF_TOOL_VERSIONS_FILENAME="global-tool-versions" run asdf global "dummy" "1.1.0" [ "$status" -eq 0 ] [ "$(cat "$HOME/$ASDF_TOOL_VERSIONS_FILENAME")" = "dummy 1.1.0" ] [ -z "$(cat "$HOME/.tool-versions")" ] unset ASDF_TOOL_VERSIONS_FILENAME } @test "global should overwrite contents of ASDF_TOOL_VERSIONS_FILENAME if set" { export ASDF_TOOL_VERSIONS_FILENAME="global-tool-versions" echo 'dummy 1.0.0' >>"$ASDF_TOOL_VERSIONS_FILENAME" run asdf global "dummy" "1.1.0" [ "$status" -eq 0 ] [ "$(cat "$HOME/$ASDF_TOOL_VERSIONS_FILENAME")" = "dummy 1.1.0" ] [ -z "$(cat "$HOME/.tool-versions")" ] unset ASDF_TOOL_VERSIONS_FILENAME } @test "local should preserve symlinks when setting versions" { mkdir other-dir touch other-dir/.tool-versions ln -s other-dir/.tool-versions .tool-versions run asdf local "dummy" "1.1.0" [ "$status" -eq 0 ] [ -L .tool-versions ] [ "$(cat other-dir/.tool-versions)" = "dummy 1.1.0" ] } @test "local should preserve symlinks when updating versions" { mkdir other-dir touch other-dir/.tool-versions ln -s other-dir/.tool-versions .tool-versions run asdf local "dummy" "1.1.0" run asdf local "dummy" "1.1.0" [ "$status" -eq 0 ] [ -L .tool-versions ] [ "$(cat other-dir/.tool-versions)" = "dummy 1.1.0" ] } @test "global should preserve symlinks when setting versions" { mkdir "$HOME/other-dir" touch "$HOME/other-dir/.tool-versions" ln -s other-dir/.tool-versions "$HOME/.tool-versions" run asdf global "dummy" "1.1.0" [ "$status" -eq 0 ] [ -L "$HOME/.tool-versions" ] [ "$(cat "$HOME/other-dir/.tool-versions")" = "dummy 1.1.0" ] } @test "global should preserve symlinks when updating versions" { mkdir "$HOME/other-dir" touch "$HOME/other-dir/.tool-versions" ln -s other-dir/.tool-versions "$HOME/.tool-versions" run asdf global "dummy" "1.1.0" run asdf global "dummy" "1.1.0" [ "$status" -eq 0 ] [ -L "$HOME/.tool-versions" ] [ "$(cat "$HOME/other-dir/.tool-versions")" = "dummy 1.1.0" ] } @test "shell wrapper function should export ENV var" { . "$(dirname "$BATS_TEST_DIRNAME")/asdf.sh" asdf shell "dummy" "1.1.0" [ "$ASDF_DUMMY_VERSION" = "1.1.0" ] unset ASDF_DUMMY_VERSION } @test "shell wrapper function with --unset should unset ENV var" { . "$(dirname "$BATS_TEST_DIRNAME")/asdf.sh" asdf shell "dummy" "1.1.0" [ "$ASDF_DUMMY_VERSION" = "1.1.0" ] asdf shell "dummy" --unset [ -z "$ASDF_DUMMY_VERSION" ] unset ASDF_DUMMY_VERSION } @test "shell wrapper function should return an error for missing plugins" { . "$(dirname "$BATS_TEST_DIRNAME")/asdf.sh" expected="No such plugin: nonexistent version 1.0.0 is not installed for nonexistent" run asdf shell "nonexistent" "1.0.0" [ "$status" -eq 1 ] [ "$output" = "$expected" ] } @test "shell should emit an error when wrapper function is not loaded" { run asdf shell "dummy" "1.1.0" [ "$status" -eq 1 ] [ "$output" = "Shell integration is not enabled. Please ensure you source asdf in your shell setup." ] } @test "export-shell-version should emit an error when plugin does not exist" { expected="No such plugin: nonexistent version 1.0.0 is not installed for nonexistent false" run asdf export-shell-version sh "nonexistent" "1.0.0" [ "$status" -eq 1 ] [ "$output" = "$expected" ] } @test "export-shell-version should emit an error when version does not exist" { expected="version nonexistent is not installed for dummy false" run asdf export-shell-version sh "dummy" "nonexistent" [ "$status" -eq 1 ] [ "$output" = "$expected" ] } @test "export-shell-version should export version if it exists" { run asdf export-shell-version sh "dummy" "1.1.0" [ "$status" -eq 0 ] [ "$output" = "export ASDF_DUMMY_VERSION=\"1.1.0\"" ] } @test "export-shell-version should use set when shell is fish" { run asdf export-shell-version fish "dummy" "1.1.0" [ "$status" -eq 0 ] [ "$output" = "set -gx ASDF_DUMMY_VERSION \"1.1.0\"" ] } @test "export-shell-version should use set-env when shell is elvish" { run asdf export-shell-version elvish "dummy" "1.1.0" [ "$status" -eq 0 ] [ "$output" = $'set-env\nASDF_DUMMY_VERSION\n1.1.0' ] } @test "export-shell-version should unset when --unset flag is passed" { run asdf export-shell-version sh "dummy" "--unset" [ "$status" -eq 0 ] [ "$output" = "unset ASDF_DUMMY_VERSION" ] } @test "export-shell-version should use set -e when --unset flag is passed and shell is fish" { run asdf export-shell-version fish "dummy" "--unset" [ "$status" -eq 0 ] [ "$output" = "set -e ASDF_DUMMY_VERSION" ] } @test "export-shell-version should use unset-env when --unset flag is passed and shell is elvish" { run asdf export-shell-version elvish "dummy" "--unset" [ "$status" -eq 0 ] [ "$output" = $'unset-env\nASDF_DUMMY_VERSION' ] } @test "[shell - dummy_plugin] wrapper function should support latest" { . "$(dirname "$BATS_TEST_DIRNAME")/asdf.sh" asdf shell "dummy" "latest" [ "$ASDF_DUMMY_VERSION" = "2.0.0" ] unset ASDF_DUMMY_VERSION } @test "[shell - dummy_legacy_plugin] wrapper function should support latest" { . "$(dirname "$BATS_TEST_DIRNAME")/asdf.sh" asdf shell "legacy-dummy" "latest" [ "$ASDF_LEGACY_DUMMY_VERSION" = "5.1.0" ] unset ASDF_LEGACY_DUMMY_VERSION } @test "[global - dummy_plugin] should support latest" { echo 'dummy 1.0.0' >>"$HOME/.tool-versions" run asdf global "dummy" "1.0.0" "latest" [ "$status" -eq 0 ] [ "$(cat "$HOME/.tool-versions")" = "dummy 1.0.0 2.0.0" ] } @test "[global - dummy_legacy_plugin] should support latest" { echo 'legacy-dummy 1.0.0' >>"$HOME/.tool-versions" run asdf global "legacy-dummy" "1.0.0" "latest" [ "$status" -eq 0 ] [ "$(cat "$HOME/.tool-versions")" = "legacy-dummy 1.0.0 5.1.0" ] } @test "[local - dummy_plugin] should support latest" { echo 'dummy 1.0.0' >>"$PROJECT_DIR/.tool-versions" run asdf local "dummy" "1.0.0" "latest" [ "$status" -eq 0 ] [ "$(cat "$PROJECT_DIR/.tool-versions")" = "dummy 1.0.0 2.0.0" ] } @test "[local - dummy_legacy_plugin] should support latest" { echo 'legacy-dummy 1.0.0' >>"$PROJECT_DIR/.tool-versions" run asdf local "legacy-dummy" "1.0.0" "latest" [ "$status" -eq 0 ] [ "$(cat "$PROJECT_DIR/.tool-versions")" = "legacy-dummy 1.0.0 5.1.0" ] } ================================================ FILE: test/where_command.bats ================================================ #!/usr/bin/env bats load test_helpers setup() { setup_asdf_dir install_dummy_plugin install_dummy_version 1.0 install_dummy_version 2.1 install_dummy_version ref-master cd "$HOME" || exit } teardown() { clean_asdf_dir } @test "where shows install location of selected version" { run asdf where 'dummy' '1.0' [ "$status" -eq 0 ] [ "$output" = "$ASDF_DIR/installs/dummy/1.0" ] } @test "where understands versions installed by ref" { run asdf where 'dummy' 'ref:master' [ "$status" -eq 0 ] [ "$output" = "$ASDF_DIR/installs/dummy/ref-master" ] } @test "where shows install location of current version if no version specified" { echo 'dummy 2.1' >>"$HOME/.tool-versions" run asdf where 'dummy' [ "$status" -eq 0 ] [ "$output" = "$ASDF_DIR/installs/dummy/2.1" ] } @test "where shows install location of first current version if not version specified and multiple current versions" { echo 'dummy 2.1 1.0' >>"$HOME/.tool-versions" run asdf where 'dummy' [ "$status" -eq 0 ] [ "$output" = "$ASDF_DIR/installs/dummy/2.1" ] } @test "where should error when the plugin doesn't exist" { run asdf where "foobar" [ "$status" -eq 1 ] [ "$output" = "No such plugin: foobar" ] } @test "where should error when version is not installed" { run asdf where 'dummy' '1.6' [ "$status" -eq 1 ] [ "$output" = "Version not installed" ] } @test "where should error when system version is set" { run asdf where 'dummy' 'system' [ "$status" -eq 1 ] [ "$output" = "System version is selected" ] } @test "where should error when no current version selected and version not specified" { run asdf where 'dummy' local expected expected="No version is set for dummy; please run \`asdf set [options] dummy <version>\`" [ "$status" -eq 1 ] [ "$output" = "$expected" ] } ================================================ FILE: test/which_command.bats ================================================ #!/usr/bin/env bats load test_helpers setup() { setup_asdf_dir install_dummy_plugin run asdf install dummy 1.0 run asdf install dummy 1.1 PROJECT_DIR="$HOME/project" mkdir -p "$PROJECT_DIR" echo 'dummy 1.0' >>"$PROJECT_DIR/.tool-versions" } teardown() { clean_asdf_dir } @test "which should show dummy 1.0 main binary" { cd "$PROJECT_DIR" run asdf which "dummy" [ "$status" -eq 0 ] [ "$output" = "$ASDF_DIR/installs/dummy/1.0/bin/dummy" ] } @test "which should fail for unknown binary" { cd "$PROJECT_DIR" run asdf which "sunny" [ "$status" -eq 1 ] [ "$output" = "unknown command: sunny. Perhaps you have to reshim?" ] } @test "which should show dummy 1.0 other binary" { cd "$PROJECT_DIR" echo "echo bin bin/subdir" >"$ASDF_DIR/plugins/dummy/bin/list-bin-paths" chmod +x "$ASDF_DIR/plugins/dummy/bin/list-bin-paths" run asdf reshim dummy 1.0 run asdf which "other_bin" [ "$status" -eq 0 ] [ "$output" = "$ASDF_DIR/installs/dummy/1.0/bin/subdir/other_bin" ] } @test "which should show path of system version" { echo 'dummy system' >"$PROJECT_DIR/.tool-versions" cd "$PROJECT_DIR" mkdir "$PROJECT_DIR/sys" touch "$PROJECT_DIR/sys/dummy" chmod +x "$PROJECT_DIR/sys/dummy" run env "PATH=$PATH:$PROJECT_DIR/sys" asdf which "dummy" [ "$status" -eq 0 ] [ "$output" = "$PROJECT_DIR/sys/dummy" ] } @test "which report when missing executable on system version" { echo 'dummy system' >"$PROJECT_DIR/.tool-versions" cd "$PROJECT_DIR" run asdf which "dummy" [ "$status" -eq 1 ] [ "$output" = "No dummy executable found for dummy system" ] } @test "which should inform when no binary is found" { cd "$PROJECT_DIR" run asdf which "bazbat" [ "$status" -eq 1 ] [ "$output" = "unknown command: bazbat. Perhaps you have to reshim?" ] } @test "which should use path returned by exec-path when present" { cd "$PROJECT_DIR" install_dummy_exec_path_script "dummy" run asdf which "dummy" [ "$status" -eq 0 ] [ "$output" = "$ASDF_DIR/installs/dummy/1.0/bin/custom/dummy" ] } @test "which should return the path set by the legacy file" { cd "$PROJECT_DIR" echo 'dummy 1.0' >>"$HOME/.tool-versions" echo '1.1' >>"$PROJECT_DIR/.dummy-version" rm "$PROJECT_DIR/.tool-versions" echo 'legacy_version_file = yes' >"$HOME/.asdfrc" run asdf which "dummy" [ "$status" -eq 0 ] [ "$output" = "$ASDF_DIR/installs/dummy/1.1/bin/dummy" ] } @test "which should not return shim path" { cd "$PROJECT_DIR" echo 'dummy 1.0' >"$PROJECT_DIR/.tool-versions" rm "$ASDF_DIR/installs/dummy/1.0/bin/dummy" run env PATH="$PATH:$ASDF_DIR/shims" asdf which dummy [ "$status" -eq 1 ] [ "$output" = "No dummy executable found for dummy 1.0" ] } ================================================ FILE: tools.go ================================================ //go:build tools package tools import ( _ "github.com/mgechev/revive" _ "honnef.co/go/tools/cmd/staticcheck" _ "mvdan.cc/gofumpt" ) ================================================ FILE: version.txt ================================================ 0.15.0