1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
//! Command-line interface definition.
//!
//! This module declares the top-level [`Cli`] struct and the [`Command`] enum
//! that together form the complete CLI surface of `gvsn`. Every subcommand,
//! flag, and argument is defined here using [`clap`]'s derive macros.
//! The doc-comment on each variant becomes the help text shown by `gvsn --help`.
use clap::{Args, Parser, Subcommand};
/// Download tuning flags shared by commands that fetch files from the network.
///
/// Embed with `#[command(flatten)]` on any subcommand that downloads Go
/// archives, source tarballs, or the gvsn binary itself.
#[derive(Args, Clone, Debug)]
pub struct DownloadArgs {
/// Maximum retry attempts on network failure (default: 3).
///
/// Retries use exponential back-off (1 s, 2 s, 4 s, …). Set to `0` to
/// fail immediately without retrying.
#[arg(long, default_value = "3", value_name = "N")]
pub retries: u8,
}
/// Top-level CLI structure parsed from `argv`.
#[derive(Parser)]
#[command(
name = "gvsn",
version = env!("CARGO_PKG_VERSION"),
about = "A fast, cross-platform Go version manager",
)]
pub struct Cli {
#[command(subcommand)]
pub command: Command,
/// Print HTTP request/response details (method, URL, status, headers).
///
/// Useful for diagnosing network issues, proxy behaviour, or unexpected
/// server responses. Output is written to stderr so it does not interfere
/// with commands that emit machine-readable text to stdout.
///
/// On `gvsn build` this flag additionally streams the Go compiler's
/// output live instead of showing a progress message.
#[arg(long, short = 'v', global = true)]
pub verbose: bool,
}
/// All subcommands exposed by `gvsn`.
#[derive(Subcommand)]
pub enum Command {
/// Compile a Go version from source and install it.
///
/// Downloads the official Go source tarball from go.dev, locates a
/// bootstrap compiler (or downloads one temporarily), then runs the
/// platform build script (`src/make.bash` on Unix, `src/make.bat` on
/// Windows) to produce a fully functional toolchain installed into
/// `~/.gvsn/versions/go<X>.<Y>.<Z>/`.
///
/// # Examples
///
/// ```text
/// gvsn build 1.25.0
/// gvsn build 1.25.0 --no-cgo
/// gvsn build 1.25.0 --bootstrap 1.22.6
/// gvsn build 1.25.0 --env GOAMD64=v3 --env CC=clang
/// ```
///
/// **Note**: building from source takes 5-15 minutes and requires ~3 GB of
/// disk space. Pass the global `-v`/`--verbose` flag to stream the
/// compiler's output live instead of a progress message.
Build {
/// Version spec to build: an exact version (`1.22.4`), a minor range
/// (`1.22`), or the keyword `latest`.
version: String,
/// Rebuild even if already installed.
#[arg(long, short = 'f')]
force: bool,
/// Disable CGO during compilation (`CGO_ENABLED=0`).
#[arg(long)]
no_cgo: bool,
/// Bootstrap Go version to use as the host compiler.
///
/// Must be installed via `gvsn install`. Defaults to the highest
/// installed version; downloads a temporary bootstrap if none exists.
#[arg(long, value_name = "VERSION")]
bootstrap: Option<String>,
/// Set an environment variable for the build (e.g. `GOAMD64=v3`).
///
/// May be repeated: `--env GOAMD64=v3 --env CC=clang`.
#[arg(long = "env", value_name = "KEY=VALUE")]
env_vars: Vec<String>,
#[command(flatten)]
download: DownloadArgs,
},
/// Install a Go version (e.g. `gvsn install 1.22.4` or `gvsn install latest`).
Install {
/// Version spec to install: an exact version (`1.22.4`), a minor range
/// (`1.22`), or the keyword `latest`.
version: String,
/// Reinstall the version even if it is already present on disk.
#[arg(long, short = 'f')]
force: bool,
#[command(flatten)]
download: DownloadArgs,
},
/// Set the global default Go version.
///
/// The version must already be installed. Use `gvsn install <version>` first.
Use {
/// Version spec to activate globally.
version: String,
},
/// Set the global default Go version (alias for `use`).
Default {
/// Version spec to activate globally.
version: String,
},
/// Pin a Go version for the current project by writing a `.go-version` file.
///
/// The file is placed in the current working directory and can be committed
/// to version control so all contributors use the same toolchain.
Local {
/// Version spec to pin (`1.22`, `1.22.4`, or `latest`).
version: String,
},
/// Remove an installed Go version from disk.
///
/// A confirmation prompt is shown unless `--force` is passed. The command
/// always refuses to remove the currently active version, regardless of
/// `--force`.
Uninstall {
/// Version spec to remove.
version: String,
/// Skip the confirmation prompt and remove the version immediately.
#[arg(long, short = 'f')]
force: bool,
},
/// List all locally installed Go versions.
List {
/// Print the list as JSON instead of formatted text, for scripting.
#[arg(long)]
json: bool,
},
/// List available Go versions from go.dev.
#[command(name = "list-remote")]
ListRemote {
/// Show every patch release instead of only the latest patch per minor.
#[arg(long)]
all: bool,
/// Print the list as JSON instead of formatted text, for scripting.
#[arg(long)]
json: bool,
},
/// Print the currently active Go version and its source.
///
/// The source is either `local (.go-version)` when a project pin is active
/// or `global` when the system-wide default is used.
Current,
/// Print the `bin/` directory path for the active (or specified) Go version.
///
/// Output is a plain path suitable for shell capture, e.g.
/// `export PATH="$(gvsn path):$PATH"`.
Path {
/// Optional version spec. Defaults to the currently active version.
version: Option<String>,
},
/// Print shell initialisation commands that configure `PATH` and `GOROOT`.
///
/// Pipe the output to your shell's eval mechanism so the active Go version
/// is applied to the current session:
///
/// - Bash / Zsh: `eval "$(gvsn env --shell bash)"`
/// - Fish: `gvsn env --shell fish | source`
/// - PowerShell: `gvsn env --shell powershell | Out-String | Invoke-Expression`
Env {
/// Target shell. Auto-detected when omitted.
/// Accepted values: `powershell`, `bash`, `zsh`, `fish`.
#[arg(long)]
shell: Option<String>,
},
/// Configure the shell environment for gvsn.
///
/// Injects the `gvsn env` hook into the shell profile, adds a static PATH
/// entry to the login profile (Linux/macOS) or the Windows registry so that
/// `go` is visible to all applications including GUI editors like VSCode.
///
/// Re-running is safe: existing up-to-date blocks are left unchanged and
/// stale ones are updated automatically.
///
/// Pass `--reset` to strip all previous gvsn configuration and re-apply it
/// cleanly. This is safe: only gvsn-managed blocks (marked with `# gvsn ...`)
/// are touched; all other content in profile files is preserved.
Setup {
/// Target shell. Auto-detected when omitted.
/// Accepted values: `powershell`, `bash`, `zsh`, `fish`.
#[arg(long)]
shell: Option<String>,
/// Remove all previous gvsn configuration and re-apply it cleanly.
#[arg(long)]
reset: bool,
},
/// Run a command using a specific Go version without changing the global default.
///
/// The chosen version's `bin/` directory is prepended to `PATH` and `GOROOT`
/// is set for the duration of the subprocess only.
///
/// # Example
///
/// ```text
/// gvsn exec 1.21 go test ./...
/// ```
Exec {
/// Version spec to use for this invocation.
version: String,
/// Command and its arguments to execute.
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
args: Vec<String>,
},
/// Diagnose the gvsn environment and report configuration issues.
///
/// Exits with status code `1` if any issue is found, making it suitable
/// for use in CI health checks.
Doctor {
/// Target shell for profile check. Auto-detected when omitted.
#[arg(long)]
shell: Option<String>,
/// Automatically repair fixable issues (missing/stale shell hook,
/// missing `~/.gvsn/current` link) instead of only reporting them.
#[arg(long)]
fix: bool,
},
/// Print a shell completion script to stdout.
///
/// Redirect the output to the appropriate location for your shell.
Completions {
/// Target shell: `bash`, `zsh`, `fish`, or `powershell`.
shell: String,
},
/// Check which installed Go versions have newer patch releases available.
///
/// Queries go.dev and compares each locally installed version against the
/// latest available patch for the same major.minor line. Versions that are
/// behind are highlighted so you can decide whether to update or remove them.
Outdated {
/// Print the report as JSON instead of formatted text, for scripting.
#[arg(long)]
json: bool,
},
/// Remove installed Go versions that are no longer referenced.
///
/// A version is considered referenced when it matches the global default
/// (`~/.gvsn/version`), appears in a `.go-version` file found by walking up
/// from the current directory, or appears in a `.go-version` file inside
/// `--scan-dir`. Everything else is offered for removal.
Prune {
/// Skip the confirmation prompt and remove unreferenced versions immediately.
#[arg(long, short = 'f')]
force: bool,
/// Print what would be removed without actually removing anything.
#[arg(long, short = 'n')]
dry_run: bool,
/// Additional directory to scan recursively for `.go-version` files
/// (up to 5 levels deep). Useful when your projects live outside the
/// current working directory tree.
#[arg(long)]
scan_dir: Option<String>,
},
/// Activate a Go version for the current shell session only.
///
/// Unlike `gvsn use`, this command does not write any files. The activation
/// lasts only for the current terminal session (or until `--unset` is run).
/// The `_gvsn_hook` respects this override and skips automatic switching
/// while `GVSN_SHELL_VERSION` is set.
///
/// # Examples
///
/// ```text
/// gvsn shell 1.21 # activate 1.21 for this session
/// gvsn shell --unset # revert to .go-version / global default
/// ```
///
/// **Note**: the shell wrapper injected by `gvsn setup` must be active for
/// this command to take effect immediately. Without it you must manually
/// run `eval "$(gvsn shell 1.21)"`.
Shell {
/// Version spec to activate for this session only.
version: Option<String>,
/// Clear the session-scoped override and revert to the file-based version.
#[arg(long)]
unset: bool,
/// Target shell for the output format. Auto-detected when omitted.
#[arg(long)]
shell: Option<String>,
},
/// Update gvsn itself to the latest release from GitHub.
///
/// Downloads the correct binary for the current platform from
/// `github.com/jhonsferg/gvsn` and replaces the running executable
/// in-place. The operation is atomic on Unix and best-effort on Windows.
Upgrade {
/// Re-install the latest version even if gvsn is already up to date.
#[arg(long, short = 'f')]
force: bool,
#[command(flatten)]
download: DownloadArgs,
},
/// Completely remove gvsn and all installed Go versions from the system.
///
/// Deletes the gvsn data directory (`~/.gvsn`), the `gvsn` binary, and every
/// gvsn-managed line from the detected shell's profile. A confirmation
/// prompt is shown unless `--force` is passed.
Implode {
/// Skip the confirmation prompt and remove everything immediately.
#[arg(long, short = 'f')]
force: bool,
},
}