Skip to content

Commit a1d4017

Browse files
committed
feat: Interactive + auto-install setup workflows for goenv + MASSIVE consolidation.
- Added InteractiveSetup and AutoInstallSetup structs to handle user interactions and automatic installations. - Implemented version discovery from .go-version and go.mod files. - Added prompts for installation and VS Code settings updates. - Introduced WorkflowResult and AutoInstallResult types to encapsulate outcomes of the workflows. - Enhanced error handling and user feedback throughout the setup processes. refactor: Improve command execution in build-tool script - Replaced direct exec.Command calls with utility functions for better error handling and context management. - Updated platform checks to use the new platform package for OS detection. - Streamlined directory creation and permission setting with utility functions. fix: Optimize embedded version generation script - Refactored fetchReleases function to use a utility for fetching JSON with timeout. - Improved file size reporting after generating embedded versions. chore: Enhance swap script with platform-specific checks - Updated OS checks to utilize the platform package for better readability. - Refactored file existence checks and command executions to use utility functions. test: Add test utilities for easier test file handling - Introduced WriteTestFile and StripDeprecationWarning functions to simplify test setup and output processing.
1 parent a217f78 commit a1d4017

File tree

232 files changed

+19591
-6981
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

232 files changed

+19591
-6981
lines changed

.gitattributes

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,40 @@ libexec/* linguist-language=Shell
55
src/shobj-conf/* linguist-language=Shell
66
test/libexec/* linguist-language=Shell
77

8+
# Line ending normalization for cross-platform compatibility
9+
# Ensure consistent line endings across Windows, macOS, and Linux
10+
11+
# Shell scripts always use LF (required for Unix shells)
12+
*.sh text eol=lf
13+
*.bash text eol=lf
14+
*.zsh text eol=lf
15+
*.fish text eol=lf
16+
17+
# Windows batch and PowerShell files use CRLF
18+
*.bat text eol=crlf
19+
*.cmd text eol=crlf
20+
*.ps1 text eol=crlf
21+
22+
# Go source files use LF
23+
*.go text eol=lf
24+
*.mod text eol=lf
25+
*.sum text eol=lf
26+
27+
# Documentation and config files use LF
28+
*.md text eol=lf
29+
*.txt text eol=lf
30+
*.json text eol=lf
31+
*.yaml text eol=lf
32+
*.yml text eol=lf
33+
*.toml text eol=lf
34+
Makefile text eol=lf
35+
36+
# Binary files
37+
*.exe binary
38+
*.dll binary
39+
*.so binary
40+
*.dylib binary
41+
*.a binary
42+
*.tar.gz binary
43+
*.zip binary
44+

README.md

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ This project was originally cloned from [pyenv](https://github.com/pyenv/pyenv),
4545
- Sync tools between versions with `goenv tools sync`
4646
- **Version aliases** (`goenv alias`) - create convenient shorthand names for versions
4747
- **VS Code integration** (`goenv vscode`) - sync Go settings with workspace-relative paths and security validation
48+
- **Shell prompt integration** (`goenv prompt`) - display active Go version in your shell prompt with smart caching
4849

4950
### goenv compared to others:
5051

@@ -171,6 +172,154 @@ See [Installation Guide](docs/user-guide/INSTALL.md) for platform-specific setup
171172

172173
---
173174

175+
## 🎨 Shell Prompt Integration
176+
177+
Display your active Go version directly in your shell prompt for instant visual feedback:
178+
179+
```bash
180+
# Quick setup with the interactive wizard
181+
goenv prompt config
182+
183+
# Manual setup - add to your shell config:
184+
# Bash/Zsh (~/.bashrc or ~/.zshrc)
185+
export PS1='$(goenv prompt --prefix "(" --suffix ") ") '"$PS1"
186+
187+
# Fish (~/.config/fish/config.fish)
188+
function fish_prompt
189+
set -l goenv_version (goenv prompt 2>/dev/null)
190+
test -n "$goenv_version"; and echo -n "($goenv_version) "
191+
# ... rest of your prompt
192+
end
193+
194+
# PowerShell ($PROFILE)
195+
function prompt {
196+
$goenvVersion = goenv prompt 2>$null
197+
if ($goenvVersion) {
198+
Write-Host "($goenvVersion) " -NoNewline -ForegroundColor Cyan
199+
}
200+
"PS $($PWD.Path)> "
201+
}
202+
```
203+
204+
**Features:**
205+
- **Smart caching** - minimal performance impact (< 50ms)
206+
- **Customizable format** - control prefix, suffix, and display format
207+
- **Project-aware** - show only in Go projects with `--go-project-only`
208+
- **Hide system Go** - use `--no-system` to hide when using system Go
209+
210+
**Advanced options:**
211+
```bash
212+
# Short version (1.23 instead of 1.23.2)
213+
export PS1='$(goenv prompt --short) '"$PS1"
214+
215+
# Custom format with emoji
216+
export PS1='$(goenv prompt --icon "🐹" --format "go:%s") '"$PS1"
217+
218+
# Show only in Go projects
219+
export PS1='$(goenv prompt --go-project-only) '"$PS1"
220+
221+
# Environment variable configuration
222+
export GOENV_PROMPT_PREFIX="["
223+
export GOENV_PROMPT_SUFFIX="]"
224+
export GOENV_PROMPT_FORMAT="go:%s"
225+
```
226+
227+
See `goenv prompt --help` for all options.
228+
229+
---
230+
231+
## 🎯 Interactive Mode
232+
233+
goenv adapts to your workflow with three levels of interactivity:
234+
235+
### Non-Interactive Mode (CI/Automation)
236+
Perfect for scripts and CI/CD pipelines - auto-confirms all operations:
237+
238+
```bash
239+
# Auto-confirm with --yes flag
240+
goenv install 1.23.0 --yes
241+
goenv uninstall 1.22.0 -y
242+
243+
# Or set globally
244+
export GOENV_ASSUME_YES=1
245+
goenv install 1.23.0
246+
247+
# Quiet mode (suppress output)
248+
goenv install 1.23.0 --quiet
249+
250+
# Auto-detected in CI environments (GitHub Actions, GitLab CI, etc.)
251+
```
252+
253+
### Minimal Interactive Mode (Default)
254+
Balances automation with safety - prompts only for critical operations:
255+
256+
```bash
257+
# Default behavior
258+
goenv uninstall 1.22.0
259+
# Prompt: "Really uninstall Go 1.22.0? [y/N]"
260+
261+
goenv install 1.23.0
262+
# Shows progress, offers retry on failure
263+
```
264+
265+
### Guided Interactive Mode (Learning)
266+
Helpful prompts and suggestions for new users:
267+
268+
```bash
269+
# Enable guided mode
270+
goenv install --interactive
271+
# Offers version selection, explains choices
272+
273+
goenv doctor --interactive
274+
# Offers to fix issues automatically
275+
276+
goenv use --interactive
277+
# Guides through version selection
278+
```
279+
280+
### Global Flags
281+
282+
- `--interactive` - Enable guided mode with helpful prompts
283+
- `--yes` / `-y` - Auto-confirm all prompts (non-interactive)
284+
- `--quiet` / `-q` - Suppress progress output (only show errors)
285+
286+
### Environment Variables
287+
288+
- `GOENV_ASSUME_YES=1` - Auto-confirm globally (like --yes)
289+
- `CI=true` - Automatically enables non-interactive mode
290+
291+
### Examples by Use Case
292+
293+
**Daily development**:
294+
```bash
295+
goenv install 1.23.0 # Default: shows progress, confirms if needed
296+
goenv use 1.23.0 # Installs if needed (with prompt)
297+
```
298+
299+
**Automation scripts**:
300+
```bash
301+
#!/bin/bash
302+
goenv install 1.23.0 --yes
303+
goenv global 1.23.0 --yes
304+
```
305+
306+
**CI/CD pipelines**:
307+
```yaml
308+
# goenv auto-detects CI - no flags needed
309+
- run: goenv install 1.23.0
310+
- run: goenv global 1.23.0
311+
```
312+
313+
**Learning mode**:
314+
```bash
315+
goenv install --interactive # Guided experience
316+
goenv explain # Understand version resolution
317+
```
318+
319+
📖 **Full Guide**: See [Interactive Mode Guide](docs/INTERACTIVE_MODE_GUIDE.md) for comprehensive documentation.
320+
321+
---
322+
174323
## 🪝 Hooks System
175324
176325
goenv includes a powerful hooks system that lets you automate actions at key points in the goenv lifecycle. Hooks are **declarative**, **safe**, and **cross-platform**.
@@ -236,6 +385,7 @@ goenv hooks test # Dry-run hooks without executing
236385
237386
#### Getting Started
238387
- **[Installation Guide](./docs/user-guide/INSTALL.md)** - Get started with goenv ⭐ **NEW: Windows FAQ**
388+
- **[Interactive Mode Guide](./docs/INTERACTIVE_MODE_GUIDE.md)** - CI/CD, automation, and guided workflows ⭐ **NEW**
239389
- **[Quick Reference](./docs/QUICK_REFERENCE.md)** - One-page cheat sheet ⭐ **NEW**
240390
- **[FAQ](./docs/FAQ.md)** - Frequently asked questions ⭐ **NEW**
241391
- **[What's New in Docs](./docs/WHATS_NEW_DOCUMENTATION.md)** - Recent documentation improvements ⭐ **NEW**

cmd/aliases/alias.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ package aliases
22

33
import (
44
"fmt"
5-
"sort"
5+
"slices"
66

77
cmdpkg "github.com/go-nv/goenv/cmd"
88

9-
"github.com/go-nv/goenv/internal/config"
9+
"github.com/go-nv/goenv/internal/cmdutil"
1010
"github.com/go-nv/goenv/internal/helptext"
1111
"github.com/go-nv/goenv/internal/manager"
1212
"github.com/spf13/cobra"
@@ -35,8 +35,7 @@ func init() {
3535
}
3636

3737
func runAlias(cmd *cobra.Command, args []string) error {
38-
cfg := config.Load()
39-
mgr := manager.NewManager(cfg)
38+
_, mgr := cmdutil.SetupContext()
4039

4140
switch len(args) {
4241
case 0:
@@ -69,7 +68,7 @@ func listAliases(cmd *cobra.Command, mgr *manager.Manager) error {
6968
for name := range aliases {
7069
names = append(names, name)
7170
}
72-
sort.Strings(names)
71+
slices.Sort(names)
7372

7473
// Print aliases
7574
for _, name := range names {

cmd/aliases/alias_test.go

Lines changed: 25 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88

99
"github.com/go-nv/goenv/cmd/legacy"
1010
"github.com/go-nv/goenv/internal/cmdtest"
11+
"github.com/go-nv/goenv/internal/utils"
12+
"github.com/go-nv/goenv/testing/testutil"
1113
"github.com/spf13/cobra"
1214
)
1315

@@ -94,27 +96,25 @@ func TestAliasCommand(t *testing.T) {
9496

9597
for _, tt := range tests {
9698
t.Run(tt.name, func(t *testing.T) {
97-
testRoot, cleanup := cmdtest.SetupTestEnv(t)
98-
defer cleanup()
99+
tmpDir := t.TempDir()
100+
t.Setenv(utils.GoenvEnvVarRoot.String(), tmpDir)
101+
t.Setenv(utils.GoenvEnvVarDir.String(), tmpDir)
99102

100103
// Setup test versions
101104
for _, version := range tt.setupVersions {
102-
cmdtest.CreateTestVersion(t, testRoot, version)
105+
cmdtest.CreateMockGoVersion(t, tmpDir, version)
103106
}
104107

105108
// Setup test aliases
106109
if len(tt.setupAliases) > 0 {
107-
aliasesFile := filepath.Join(testRoot, "aliases")
110+
aliasesFile := filepath.Join(tmpDir, "aliases")
108111
var content strings.Builder
109112
content.WriteString("# goenv aliases\n")
110113
content.WriteString("# Format: alias_name=target_version\n")
111114
for name, version := range tt.setupAliases {
112115
content.WriteString(name + "=" + version + "\n")
113116
}
114-
err := os.WriteFile(aliasesFile, []byte(content.String()), 0644)
115-
if err != nil {
116-
t.Fatalf("Failed to setup aliases: %v", err)
117-
}
117+
testutil.WriteTestFile(t, aliasesFile, []byte(content.String()), utils.PermFileDefault, "Failed to setup aliases")
118118
}
119119

120120
// Create and execute command
@@ -158,7 +158,7 @@ func TestAliasCommand(t *testing.T) {
158158

159159
// For set operations, verify the file was written correctly
160160
if len(tt.args) == 2 && tt.expectedError == "" {
161-
aliasesFile := filepath.Join(testRoot, "aliases")
161+
aliasesFile := filepath.Join(tmpDir, "aliases")
162162
content, err := os.ReadFile(aliasesFile)
163163
if err != nil {
164164
t.Errorf("Failed to read aliases file: %v", err)
@@ -211,27 +211,25 @@ func TestUnaliasCommand(t *testing.T) {
211211

212212
for _, tt := range tests {
213213
t.Run(tt.name, func(t *testing.T) {
214-
testRoot, cleanup := cmdtest.SetupTestEnv(t)
215-
defer cleanup()
214+
tmpDir := t.TempDir()
215+
t.Setenv(utils.GoenvEnvVarRoot.String(), tmpDir)
216+
t.Setenv(utils.GoenvEnvVarDir.String(), tmpDir)
216217

217218
// Setup test versions
218219
for _, version := range tt.setupVersions {
219-
cmdtest.CreateTestVersion(t, testRoot, version)
220+
cmdtest.CreateMockGoVersion(t, tmpDir, version)
220221
}
221222

222223
// Setup test aliases
223224
if len(tt.setupAliases) > 0 {
224-
aliasesFile := filepath.Join(testRoot, "aliases")
225+
aliasesFile := filepath.Join(tmpDir, "aliases")
225226
var content strings.Builder
226227
content.WriteString("# goenv aliases\n")
227228
content.WriteString("# Format: alias_name=target_version\n")
228229
for name, version := range tt.setupAliases {
229230
content.WriteString(name + "=" + version + "\n")
230231
}
231-
err := os.WriteFile(aliasesFile, []byte(content.String()), 0644)
232-
if err != nil {
233-
t.Fatalf("Failed to setup aliases: %v", err)
234-
}
232+
testutil.WriteTestFile(t, aliasesFile, []byte(content.String()), utils.PermFileDefault, "Failed to setup aliases")
235233
}
236234

237235
// Create and execute command
@@ -268,7 +266,7 @@ func TestUnaliasCommand(t *testing.T) {
268266

269267
// Verify the alias was removed from the file
270268
if len(tt.args) == 1 && tt.expectedError == "" {
271-
aliasesFile := filepath.Join(testRoot, "aliases")
269+
aliasesFile := filepath.Join(tmpDir, "aliases")
272270
content, err := os.ReadFile(aliasesFile)
273271
if err != nil {
274272
t.Errorf("Failed to read aliases file: %v", err)
@@ -322,20 +320,18 @@ func TestAliasResolution(t *testing.T) {
322320

323321
for _, tt := range tests {
324322
t.Run(tt.name, func(t *testing.T) {
325-
testRoot, cleanup := cmdtest.SetupTestEnv(t)
326-
defer cleanup()
323+
tmpDir := t.TempDir()
324+
t.Setenv(utils.GoenvEnvVarRoot.String(), tmpDir)
325+
t.Setenv(utils.GoenvEnvVarDir.String(), tmpDir)
327326

328327
// Setup test version
329-
cmdtest.CreateTestVersion(t, testRoot, tt.targetVersion)
328+
cmdtest.CreateMockGoVersion(t, tmpDir, tt.targetVersion)
330329

331330
// Setup alias
332-
aliasesFile := filepath.Join(testRoot, "aliases")
331+
aliasesFile := filepath.Join(tmpDir, "aliases")
333332
content := "# goenv aliases\n# Format: alias_name=target_version\n"
334333
content += tt.aliasName + "=" + tt.targetVersion + "\n"
335-
err := os.WriteFile(aliasesFile, []byte(content), 0644)
336-
if err != nil {
337-
t.Fatalf("Failed to setup aliases: %v", err)
338-
}
334+
testutil.WriteTestFile(t, aliasesFile, []byte(content), utils.PermFileDefault, "Failed to setup aliases")
339335

340336
if tt.useGlobal {
341337
// Test global command with alias
@@ -350,14 +346,14 @@ func TestAliasResolution(t *testing.T) {
350346
cmd.SetOut(stdout)
351347
cmd.SetArgs([]string{tt.aliasName})
352348

353-
err = cmd.Execute()
349+
err := cmd.Execute()
354350
if err != nil {
355351
t.Errorf("Failed to set global with alias: %v", err)
356352
return
357353
}
358354

359355
// Verify the resolved version was written
360-
globalFile := filepath.Join(testRoot, "version")
356+
globalFile := filepath.Join(tmpDir, "version")
361357
content, err := os.ReadFile(globalFile)
362358
if err != nil {
363359
t.Errorf("Failed to read global version file: %v", err)
@@ -383,7 +379,7 @@ func TestAliasResolution(t *testing.T) {
383379
cmd.SetOut(stdout)
384380
cmd.SetArgs([]string{tt.aliasName})
385381

386-
err = cmd.Execute()
382+
err := cmd.Execute()
387383
if err != nil {
388384
t.Errorf("Failed to set local with alias: %v", err)
389385
return

0 commit comments

Comments
 (0)