Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 51 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: VersionFox CI

on: [push]
on: [push, workflow_dispatch]

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
Expand All @@ -12,14 +12,13 @@ jobs:
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
go-version: [ '1.21.x' ]

steps:
- uses: actions/checkout@v7
- name: Setup Go ${{ matrix.go-version }}
uses: actions/setup-go@v4
- name: Setup Go from go.mod
uses: actions/setup-go@v7
with:
go-version: ${{ matrix.go-version }}
go-version-file: go.mod
- name: Install dependencies
run: |
go get .
Expand All @@ -31,3 +30,50 @@ jobs:
go test ./... -coverprofile=coverage.out -covermode=atomic
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v7

windows-regressions:
name: Windows shell and interrupted installation regressions
runs-on: windows-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
- uses: msys2/setup-msys2@v2
id: msys2
with:
msystem: MSYS
install: zsh
- name: Exercise native Windows processes and MSYS2 zsh
shell: pwsh
env:
MSYS2_LOCATION: ${{ steps.msys2.outputs.msys2-location }}
VFOX_TEST_MSYS_ZSH: '1'
run: |
$env:PATH = "$env:MSYS2_LOCATION\usr\bin;$env:PATH"
go test ./internal/env -run 'TestWindowsPathFormatByShell|TestMSYSZshPATH' -count=1 -v
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
go test ./internal/shell -run TestOpenSkipsVfoxShim -count=1 -v
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
go test ./internal/sdk -run '^TestInstall' -count=1 -v
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

fish-completion:
name: Fish completion integration
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v7
- uses: actions/setup-go@v7
with:
go-version-file: go.mod
- name: Install Fish
run: |
sudo apt-get update
sudo apt-get install --no-install-recommends -y fish
- name: Exercise native Fish completions
run: |
command -v fish
fish --version
go test ./cmd -run '^TestFishCompletion$' -count=1 -v
5 changes: 5 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ brews:
extra_install: |-
bash_completion.install "completions/bash_autocomplete" => "vfox"
zsh_completion.install "completions/zsh_autocomplete" => "_vfox"
fish_completion.install "completions/vfox.fish"

nfpms:
- file_name_template: >-
Expand Down Expand Up @@ -161,6 +162,10 @@ nfpms:
dst: /usr/share/zsh/site-functions/_vfox
file_info:
mode: 0644
- src: ./completions/vfox.fish
dst: /usr/share/fish/vendor_completions.d/vfox.fish
file_info:
mode: 0644
- src: ./LICENSE
dst: /usr/share/doc/vfox/copyright
file_info:
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Keep imports acyclic. Shared utilities must not depend on SDK, plugin, environme
## SDK and plugin behavior

- Installation calls `PreInstall`, prepares the main runtime and additions, then calls optional `PostInstall`. Preserve cleanup of partial directories created by a failed attempt. Uninstallation and its optional `PreUninstall` hook belong to SDK.
- Installation keeps the final payload path stable for plugin-generated paths. `Install` uses a sibling `.installing` marker until `PostInstall` completes and a sibling `.lock` file for cross-process exclusion. Preserve interrupted-install retry and legacy marker-free payloads; do not unlink the lock file after releasing it.
- Version resolution calls optional `PreUse` first. If no version is returned, use the existing exact-installed and prefix-matching logic. `IsNoResultProvided(err)` permits fallback; actual hook errors propagate. `UseWithConfig` checks that the resolved version is installed before applying scope state.
- `Current` checks the highest-priority configured version. Activation/export uses the separate installed-version fallback in [tool_resolution.go](cmd/commands/tool_resolution.go); preserve the caller-specific behavior.
- `EnvKeysForScope` passes scope link paths to the plugin and does not create links. Use the platform helpers in `env` to create/remove directory links for the main runtime and additions, preserving links that already point to the correct target.
Expand Down
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ and switch between different environment you need via the command line.
- simple **plugin system** to add support for your runtime of choice
- **automatically switches** runtime versions as you traverse your project
- support for existing config files `.node-version`, `.nvmrc`, `.sdkmanrc` for easy migration
- shell completion available for common shells (Bash, ZSH, Powershell, Clink)
- shell completion available for common shells (Bash, ZSH, Fish, Powershell, Clink)

## Demo

Expand Down Expand Up @@ -62,6 +62,15 @@ vfox activate nushell $nu.default-config-dir | save --append $nu.config-path

> Remember to restart your shell to apply the changes.

Fish completion suggests commands and options. Homebrew, DEB, and RPM packages
install it automatically. For archive or source installations, run the following
from the extracted archive or repository directory:

```fish
mkdir -p ~/.config/fish/completions
cp completions/vfox.fish ~/.config/fish/completions/vfox.fish
```

#### 3. Add an SDK plugin

```bash
Expand Down
10 changes: 9 additions & 1 deletion README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
- 简单的 **插件系统** 来添加对你选择的语言的支持
- 在您切换项目时, 帮您**自动切换**运行时版本
- 支持现有配置文件 `.node-version`、`.nvmrc`、`.sdkmanrc`,以方便迁移
- 支持常用Shell(Powershell、Bash、ZSH),并提供补全功能
- 支持常用 Shell(Powershell、Bash、ZSH、Fish、Clink),并提供补全功能

## 演示

Expand Down Expand Up @@ -57,6 +57,14 @@ vfox activate nushell $nu.default-config-dir | save --append $nu.config-path

> 请记住重启你的 Shell 以应用更改。

Fish 补全支持命令和选项。Homebrew、DEB 和 RPM 软件包会自动安装补全脚本。
如果通过压缩包或源码安装,请在解压后的目录或仓库目录中运行:

```fish
mkdir -p ~/.config/fish/completions
cp completions/vfox.fish ~/.config/fish/completions/vfox.fish
```

#### 3.添加插件
```bash
$ vfox add nodejs
Expand Down
10 changes: 5 additions & 5 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"os"

"github.com/urfave/cli/v3"

"github.com/version-fox/vfox/cmd/commands"
"github.com/version-fox/vfox/internal"
"github.com/version-fox/vfox/internal/shared/logger"
Expand Down Expand Up @@ -64,11 +65,7 @@ func newCmd() *cmd {
app.Version = version
app.Description = "vfox is a cross-platform version manager, extendable via plugins. It allows you to quickly install and switch between different environment you need via the command line."
app.Suggest = true
app.ShellComplete = func(ctx context.Context, cmd *cli.Command) {
for _, command := range cmd.Commands {
_, _ = fmt.Fprintln(cmd.Writer, command.Name)
}
}
app.ShellComplete = completeCommand

debugFlags := &cli.BoolFlag{
Name: "debug",
Expand Down Expand Up @@ -102,6 +99,9 @@ func newCmd() *cmd {
commands.Exec,
commands.Cd,
}
for _, command := range app.Commands {
command.ShellComplete = completeCommand
}

return &cmd{app: app, version: version}
}
35 changes: 35 additions & 0 deletions cmd/completion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2026 Han Li and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package cmd

import (
"context"

"github.com/urfave/cli/v3"
)

func completeCommand(ctx context.Context, command *cli.Command) {
// urfave/cli's subcommand completer reads parsed positional arguments,
// which can omit partial flags or truncate at "-". A parentless view uses
// the original os.Args instead, including the prefix being completed.
completion := &cli.Command{
Flags: command.Flags,
Commands: command.Commands,
Writer: command.Root().Writer,
}
cli.DefaultCompleteWithFlags(ctx, completion)
}
Loading
Loading