Skip to content

Commit f685b43

Browse files
CopilotaooohanCopilot
authored
feat: support unuse command to remove version settings from different scopes (#541)
* Initial plan * Initial analysis and project setup Co-authored-by: aooohan <40265686+aooohan@users.noreply.github.com> * Implement unuse command with scope support Co-authored-by: aooohan <40265686+aooohan@users.noreply.github.com> * Add tests for unuse command functionality Co-authored-by: aooohan <40265686+aooohan@users.noreply.github.com> * Add comprehensive documentation for unuse command Co-authored-by: aooohan <40265686+aooohan@users.noreply.github.com> * Update docs/zh-hans/usage/all-commands.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update core-commands.md * Update core-commands.md * Delete unuse command section from quick-start guide Remove section on unsetting runtime SDK versions. * Update quick-start.md --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: aooohan <40265686+aooohan@users.noreply.github.com> Co-authored-by: Han Li <lihan@apache.org> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 3833412 commit f685b43

11 files changed

Lines changed: 382 additions & 5 deletions

File tree

cmd/cmd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ func newCmd() *cmd {
8686
commands.Install,
8787
commands.Current,
8888
commands.Use,
89+
commands.Unuse,
8990
commands.List,
9091
commands.Uninstall,
9192
commands.Available,

cmd/commands/unuse.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright 2025 Han Li and contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package commands
18+
19+
import (
20+
"fmt"
21+
22+
"github.com/urfave/cli/v2"
23+
"github.com/version-fox/vfox/internal"
24+
"github.com/version-fox/vfox/internal/base"
25+
)
26+
27+
var Unuse = &cli.Command{
28+
Name: "unuse",
29+
Usage: "Unset a version of the target SDK",
30+
Flags: []cli.Flag{
31+
&cli.BoolFlag{
32+
Name: "global",
33+
Aliases: []string{"g"},
34+
Usage: "Unset from the global environment",
35+
},
36+
&cli.BoolFlag{
37+
Name: "project",
38+
Aliases: []string{"p"},
39+
Usage: "Unset from the current directory",
40+
},
41+
&cli.BoolFlag{
42+
Name: "session",
43+
Aliases: []string{"s"},
44+
Usage: "Unset from the current shell session",
45+
},
46+
},
47+
Action: unuseCmd,
48+
Category: CategorySDK,
49+
}
50+
51+
func unuseCmd(ctx *cli.Context) error {
52+
sdkName := ctx.Args().First()
53+
if len(sdkName) == 0 {
54+
return fmt.Errorf("invalid parameter. format: <sdk-name>")
55+
}
56+
57+
scope := base.Session
58+
if ctx.IsSet("global") {
59+
scope = base.Global
60+
} else if ctx.IsSet("project") {
61+
scope = base.Project
62+
} else {
63+
scope = base.Session
64+
}
65+
66+
manager := internal.NewSdkManager()
67+
defer manager.Close()
68+
69+
source, err := manager.LookupSdk(sdkName)
70+
if err != nil {
71+
return fmt.Errorf("%s not supported, error: %w", sdkName, err)
72+
}
73+
74+
return source.Unuse(scope)
75+
}

cmd/commands/unuse_test.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/*
2+
* Copyright 2025 Han Li and contributors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package commands
18+
19+
import (
20+
"testing"
21+
22+
"github.com/version-fox/vfox/internal/base"
23+
)
24+
25+
func TestUnuseScopeSelection(t *testing.T) {
26+
// Test that scope selection logic works correctly
27+
tests := []struct {
28+
name string
29+
globalSet bool
30+
projectSet bool
31+
sessionSet bool
32+
expectedScope base.UseScope
33+
}{
34+
{
35+
name: "Default to session scope",
36+
globalSet: false,
37+
projectSet: false,
38+
sessionSet: false,
39+
expectedScope: base.Session,
40+
},
41+
{
42+
name: "Global scope when global flag set",
43+
globalSet: true,
44+
projectSet: false,
45+
sessionSet: false,
46+
expectedScope: base.Global,
47+
},
48+
{
49+
name: "Project scope when project flag set",
50+
globalSet: false,
51+
projectSet: true,
52+
sessionSet: false,
53+
expectedScope: base.Project,
54+
},
55+
{
56+
name: "Session scope when session flag set",
57+
globalSet: false,
58+
projectSet: false,
59+
sessionSet: true,
60+
expectedScope: base.Session,
61+
},
62+
{
63+
name: "Global takes precedence over project",
64+
globalSet: true,
65+
projectSet: true,
66+
sessionSet: false,
67+
expectedScope: base.Global,
68+
},
69+
{
70+
name: "Global takes precedence over session",
71+
globalSet: true,
72+
projectSet: false,
73+
sessionSet: true,
74+
expectedScope: base.Global,
75+
},
76+
{
77+
name: "Project takes precedence over session",
78+
globalSet: false,
79+
projectSet: true,
80+
sessionSet: true,
81+
expectedScope: base.Project,
82+
},
83+
{
84+
name: "Global takes precedence over all",
85+
globalSet: true,
86+
projectSet: true,
87+
sessionSet: true,
88+
expectedScope: base.Global,
89+
},
90+
}
91+
92+
for _, tt := range tests {
93+
t.Run(tt.name, func(t *testing.T) {
94+
// Simulate the scope selection logic from the unuse command
95+
scope := base.Session
96+
if tt.globalSet {
97+
scope = base.Global
98+
} else if tt.projectSet {
99+
scope = base.Project
100+
} else {
101+
scope = base.Session
102+
}
103+
104+
if scope != tt.expectedScope {
105+
t.Errorf("Expected scope %v, but got %v", tt.expectedScope, scope)
106+
}
107+
})
108+
}
109+
}
110+
111+
func TestUnuseCommandValidation(t *testing.T) {
112+
// Test input validation logic
113+
tests := []struct {
114+
name string
115+
sdkName string
116+
expectError bool
117+
errorMsg string
118+
}{
119+
{
120+
name: "Valid SDK name",
121+
sdkName: "nodejs",
122+
expectError: false,
123+
errorMsg: "",
124+
},
125+
{
126+
name: "Empty SDK name",
127+
sdkName: "",
128+
expectError: true,
129+
errorMsg: "invalid parameter. format: <sdk-name>",
130+
},
131+
{
132+
name: "SDK name with special characters",
133+
sdkName: "node-js",
134+
expectError: false,
135+
errorMsg: "",
136+
},
137+
{
138+
name: "SDK name with numbers",
139+
sdkName: "java8",
140+
expectError: false,
141+
errorMsg: "",
142+
},
143+
}
144+
145+
for _, tt := range tests {
146+
t.Run(tt.name, func(t *testing.T) {
147+
// Simulate the validation logic from the unuse command
148+
hasError := len(tt.sdkName) == 0
149+
150+
if hasError != tt.expectError {
151+
t.Errorf("Expected error: %v, but got error: %v", tt.expectError, hasError)
152+
}
153+
})
154+
}
155+
}
156+
157+
func TestScopeStringMapping(t *testing.T) {
158+
// Test that scope values map correctly to their string representations
159+
tests := []struct {
160+
scope base.UseScope
161+
expected string
162+
}{
163+
{base.Global, "global"},
164+
{base.Project, "project"},
165+
{base.Session, "session"},
166+
}
167+
168+
for _, tt := range tests {
169+
t.Run(tt.expected, func(t *testing.T) {
170+
actual := tt.scope.String()
171+
if actual != tt.expected {
172+
t.Errorf("Expected scope string %q, but got %q", tt.expected, actual)
173+
}
174+
})
175+
}
176+
}

docs/guides/quick-start.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,6 @@ When you close the current terminal, `vfox` will **automatically switch back to
270270
:::
271271

272272

273-
274273
## Demo
275274

276275
::: tip

docs/usage/all-commands.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ vfox search <sdk-name> Search available versions of a SDK
1111
vfox install <sdk-name>@<version> Install the specified version of SDK
1212
vfox uninstall <sdk-name>@<version> Uninstall the specified version of SDK
1313
vfox use [--global --project --session] <sdk-name>[@<version>] Use the specified version of SDK for different scope
14+
vfox unuse [--global --project --session] <sdk-name> Unset the version of SDK from specified scope
1415
vfox list [<sdk-name>] List all installed versions of SDK
1516
vfox current [<sdk-name>] Show the current version of SDK
1617
vfox config [<key>] [<value>] Setup, view config

docs/usage/core-commands.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,35 @@ vfox u [options] <sdk-name>[@<version>]
101101
`Unix-like`: `Session` scope
102102
:::
103103

104+
## Unuse <Badge type="tip" text=">= 0.9.0" vertical="middle" />
105+
106+
Unset the runtime version from a specific scope.
107+
108+
**Usage**
109+
110+
```shell
111+
vfox unuse [options] <sdk-name>
112+
```
113+
114+
`sdk-name`: SDK name
115+
116+
**Options**
117+
118+
- `-g, --global`: Remove from global scope
119+
- `-p, --project`: Remove from project scope (current directory)
120+
- `-s, --session`: Remove from session scope (current Shell session)
121+
122+
::: tip Default scope
123+
124+
`Windows`: `Global` scope
125+
126+
`Unix-like`: `Session` scope
127+
:::
128+
129+
::: warning Effect
130+
After using `unuse`, the SDK will no longer be active in the specified scope. If the SDK is configured in other scopes, those will take precedence according to vfox's scope hierarchy (Session > Project > Global).
131+
:::
132+
104133
## Uninstall
105134

106135
Uninstall the specified version of the SDK.
@@ -165,4 +194,4 @@ Upgrade `vfox` to the latest version.
165194

166195
```shell
167196
vfox upgrade
168-
```
197+
```

docs/zh-hans/guides/quick-start.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,6 @@ $ vfox use -s nodejs
263263
:::
264264
265265
266-
267266
## 效果演示
268267
269268
::: tip

docs/zh-hans/usage/all-commands.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ vfox search <sdk-name> Search available versions of a SDK
1111
vfox install <sdk-name>@<version> Install the specified version of SDK
1212
vfox uninstall <sdk-name>@<version> Uninstall the specified version of SDK
1313
vfox use [--global --project --session] <sdk-name>[@<version>] Use the specified version of SDK for different scope
14+
vfox unuse [--global --project --session] <sdk-name> Unset the SDK version from the specified scope
1415
vfox list [<sdk-name>] List all installed versions of SDK
1516
vfox current [<sdk-name>] Show the current version of SDK
1617
vfox config [<key>] [<value>] Setup, view config

docs/zh-hans/usage/core-commands.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,34 @@ vfox u [options] <sdk-name>[@<version>]
103103

104104
:::
105105

106+
## Unuse <Badge type="tip" text=">= 0.9.0" vertical="middle" />
107+
108+
从指定作用域取消设置运行时版本
109+
110+
**用法**
111+
112+
```shell
113+
vfox unuse [options] <sdk-name>
114+
```
115+
116+
`sdk-name`: SDK 名称
117+
118+
**选项**
119+
120+
- `-g, --global`: 从全局作用域移除
121+
- `-p, --project`: 从项目作用域移除(当前目录)
122+
- `-s, --session`: 从会话作用域移除(当前 Shell 会话)
123+
124+
::: tip 默认作用域
125+
`Windows`: 默认`Global`作用域
126+
127+
`Unix-like`: 默认`Session`作用域
128+
:::
129+
130+
::: warning 效果
131+
使用 `unuse` 后,SDK 将不再在指定作用域中处于活动状态。如果 SDK 在其他作用域中配置,那些将根据 vfox 的作用域层次结构优先生效(Session > Project > Global)。
132+
:::
133+
106134
## Uninstall
107135

108136
卸载指定版本的 SDK。

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N
6060
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
6161
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
6262
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
63-
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
64-
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
6563
github.com/ebitengine/purego v0.8.3 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc=
6664
github.com/ebitengine/purego v0.8.3/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
6765
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=

0 commit comments

Comments
 (0)