Skip to content

Commit 473e724

Browse files
authored
Merge pull request #333 from lets-cli/codex/make-upgrade-selfcommand-308
Replace root upgrade flag with self upgrade command
2 parents 5c6d078 + 308ae43 commit 473e724

16 files changed

Lines changed: 170 additions & 44 deletions

docs/docs/architecture.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ To achieve this we are creating so-called `root` command and `subcommands` from
7171
#### Root command
7272

7373
Root command is responsible for:
74-
- `lets` own command line flags such as `--version`, `--upgrade`, `--help` and so on.
74+
- `lets` own command line flags such as `--version`, `--help` and so on.
75+
- `lets self` subcommands such as `lets self upgrade`
7576
- `lets` commands autocompletion in terminal
7677

7778
#### Subcommands

docs/docs/changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ title: Changelog
2222
* `[Fixed]` Resolve `go to definition` from YAML merge aliases such as `<<: *test` to the referenced command in `lets self lsp`.
2323
* `[Fixed]` Resolve `go to definition` from command references such as `ref: build` to the referenced command in `lets self lsp`.
2424
* `[Added]` Load local mixin files into LSP storage and command index so mixin commands are available for navigation.
25+
* `[Changed]` Replace the top-level `--upgrade` flag with the `lets self upgrade` command.
2526

2627
## [0.0.59](https://github.com/lets-cli/lets/releases/tag/v0.0.59)
2728

docs/docs/cli.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ title: CLI options
1212
|`--init`|`bool`|false|creates a new lets.yaml in the current folder|
1313
|`--only`|`stringArray`||run only specified command(s) described in cmd as map|
1414
|`--exclude`|`stringArray`||run all but excluded command(s) described in cmd as map|
15-
|`--upgrade`|`bool`|false|upgrade lets to latest version|
1615
|`--no-depends`|`bool`|false|skip 'depends' for running command|
1716
|`-c, --config`|`string`|lets.yaml|specify config|
1817
|`-d, --debug`|`bool`|false|verbose logs|
1918
|`-h, --help`|||help for lets|
2019
|`-v, --version`|||version for lets|
20+
21+
Upgrade the lets binary with `lets self upgrade`.

docs/docs/installation.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,13 @@ brew install lets-cli/tap/lets
126126
>
127127
<TabItem value="self">
128128

129-
Starting from version `0.0.30` lets has `--upgrade` flag which allows to do self-upgrades.
129+
Starting from version `0.0.30` lets has a built-in self-upgrade command.
130130

131131
It updates binary located at `which lets`
132132

133133

134134
```bash
135-
lets --upgrade
135+
lets self upgrade
136136
```
137137

138138
If your `lets` version is below `0.0.30` - use shell script and specify the latest version.

internal/cli/cli.go

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -112,20 +112,6 @@ func Main(version string, buildDate string) int {
112112
return 0
113113
}
114114

115-
if rootFlags.upgrade {
116-
upgrader, err := upgrade.NewBinaryUpgrader(registry.NewGithubRegistry(), version)
117-
if err == nil {
118-
err = upgrader.Upgrade(ctx)
119-
}
120-
121-
if err != nil {
122-
log.Errorf("can not self-upgrade binary: %s", err)
123-
return 1
124-
}
125-
126-
return 0
127-
}
128-
129115
showUsage := rootFlags.help || (command.Name() == "help" && len(args) == 0) || (len(os.Args) == 1)
130116

131117
if showUsage {
@@ -204,6 +190,10 @@ func allowsMissingConfig(current *cobra.Command) bool {
204190
return true
205191
}
206192

193+
return isSelfCommand(current)
194+
}
195+
196+
func isSelfCommand(current *cobra.Command) bool {
207197
for cmd := current; cmd != nil; cmd = cmd.Parent() {
208198
parent := cmd.Parent()
209199
if cmd.Name() == "self" && parent != nil && parent.Name() == "lets" {
@@ -220,7 +210,7 @@ func maybeStartUpdateCheck(
220210
command *cobra.Command,
221211
appSettings settings.Settings,
222212
) (<-chan updateCheckResult, context.CancelFunc) {
223-
if !shouldCheckForUpdate(command.Name(), isInteractiveStderr(), appSettings) {
213+
if !shouldCheckForUpdate(command, isInteractiveStderr(), appSettings) {
224214
return nil, func() {}
225215
}
226216

@@ -273,17 +263,21 @@ func printUpdateNotice(updateCh <-chan updateCheckResult) {
273263
}
274264
}
275265

276-
func shouldCheckForUpdate(commandName string, interactive bool, appSettings settings.Settings) bool {
266+
func shouldCheckForUpdate(command *cobra.Command, interactive bool, appSettings settings.Settings) bool {
277267
if !interactive || !appSettings.UpgradeNotify || os.Getenv("CI") != "" {
278268
return false
279269
}
280270

281-
switch commandName {
282-
case "completion", "help", "lsp", "self":
283-
return false
284-
default:
271+
if command == nil {
285272
return true
286273
}
274+
275+
switch command.Name() {
276+
case "completion", "help":
277+
return false
278+
}
279+
280+
return !isSelfCommand(command)
287281
}
288282

289283
func isInteractiveStderr() bool {
@@ -298,7 +292,6 @@ type flags struct {
298292
version bool
299293
all bool
300294
init bool
301-
upgrade bool
302295
}
303296

304297
// We can not parse --config and --debug flags using cobra.Command.ParseFlags
@@ -379,9 +372,7 @@ func parseRootFlags(args []string) (*flags, error) {
379372
f.init = true
380373
}
381374
case "--upgrade":
382-
if !isFlagVisited("upgrade") {
383-
f.upgrade = true
384-
}
375+
return nil, errors.New("--upgrade has been replaced with 'lets self upgrade'")
385376
}
386377

387378
idx += 1 //nolint:revive,golint

internal/cli/cli_test.go

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,20 +65,20 @@ func TestShouldCheckForUpdate(t *testing.T) {
6565
t.Run("should allow normal interactive commands", func(t *testing.T) {
6666
t.Setenv("CI", "")
6767

68-
if !shouldCheckForUpdate("lets", true, defaultSettings) {
68+
if !shouldCheckForUpdate(&cobra.Command{Use: "run"}, true, defaultSettings) {
6969
t.Fatal("expected update check to be enabled")
7070
}
7171
})
7272

7373
t.Run("should skip non interactive sessions", func(t *testing.T) {
74-
if shouldCheckForUpdate("lets", false, defaultSettings) {
74+
if shouldCheckForUpdate(&cobra.Command{Use: "run"}, false, defaultSettings) {
7575
t.Fatal("expected non-interactive session to skip update check")
7676
}
7777
})
7878

7979
t.Run("should skip when CI is set", func(t *testing.T) {
8080
t.Setenv("CI", "1")
81-
if shouldCheckForUpdate("lets", true, defaultSettings) {
81+
if shouldCheckForUpdate(&cobra.Command{Use: "run"}, true, defaultSettings) {
8282
t.Fatal("expected CI to skip update check")
8383
}
8484
})
@@ -87,16 +87,45 @@ func TestShouldCheckForUpdate(t *testing.T) {
8787
disabled := settings.Default()
8888
disabled.UpgradeNotify = false
8989

90-
if shouldCheckForUpdate("lets", true, disabled) {
90+
if shouldCheckForUpdate(&cobra.Command{Use: "run"}, true, disabled) {
9191
t.Fatal("expected disabled settings to skip update check")
9292
}
9393
})
9494

95-
t.Run("should skip internal commands", func(t *testing.T) {
96-
for _, name := range []string{"completion", "help", "lsp", "self"} {
97-
if shouldCheckForUpdate(name, true, defaultSettings) {
98-
t.Fatalf("expected %q to skip update check", name)
95+
t.Run("should skip completion and help commands", func(t *testing.T) {
96+
for _, command := range []*cobra.Command{{Use: "completion"}, {Use: "help"}} {
97+
if shouldCheckForUpdate(command, true, defaultSettings) {
98+
t.Fatalf("expected %q to skip update check", command.Name())
9999
}
100100
}
101101
})
102+
103+
t.Run("should skip self subcommands", func(t *testing.T) {
104+
root := cmdpkg.CreateRootCommand("v0.0.0-test", "")
105+
cmdpkg.InitSelfCmd(root, "v0.0.0-test")
106+
107+
for _, args := range [][]string{{"self"}, {"self", "doc"}, {"self", "upgrade"}} {
108+
command, _, err := root.Find(args)
109+
if err != nil {
110+
t.Fatalf("unexpected error for %v: %v", args, err)
111+
}
112+
113+
if shouldCheckForUpdate(command, true, defaultSettings) {
114+
t.Fatalf("expected %v to skip update check", args)
115+
}
116+
}
117+
})
118+
}
119+
120+
func TestParseRootFlags(t *testing.T) {
121+
t.Run("should reject legacy upgrade flag", func(t *testing.T) {
122+
_, err := parseRootFlags([]string{"--upgrade"})
123+
if err == nil {
124+
t.Fatal("expected legacy upgrade flag error")
125+
}
126+
127+
if err.Error() != "--upgrade has been replaced with 'lets self upgrade'" {
128+
t.Fatalf("unexpected error: %v", err)
129+
}
130+
})
102131
}

internal/cmd/root.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,6 @@ func initRootFlags(rootCmd *cobra.Command) {
9898
rootCmd.Flags().StringToStringP("env", "E", nil, "set env variable for running command KEY=VALUE")
9999
rootCmd.Flags().StringArray("only", []string{}, "run only specified command(s) described in cmd as map")
100100
rootCmd.Flags().StringArray("exclude", []string{}, "run all but excluded command(s) described in cmd as map")
101-
rootCmd.Flags().Bool("upgrade", false, "upgrade lets to latest version")
102101
rootCmd.Flags().Bool("init", false, "create a new lets.yaml in the current folder")
103102
rootCmd.Flags().Bool("no-depends", false, "skip 'depends' for running command")
104103
rootCmd.Flags().CountP("debug", "d", "show debug logs (or use LETS_DEBUG=1). If used multiple times, shows more verbose logs") //nolint:lll

internal/cmd/root_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ package cmd
22

33
import (
44
"bytes"
5+
"context"
56
"errors"
67
"strings"
78
"testing"
89

910
"github.com/lets-cli/lets/internal/config/config"
11+
"github.com/lets-cli/lets/internal/upgrade"
1012
"github.com/spf13/cobra"
1113
)
1214

@@ -298,4 +300,71 @@ func TestSelfCmd(t *testing.T) {
298300
t.Fatalf("expected no suggestions, got %q", err.Error())
299301
}
300302
})
303+
304+
t.Run("should run self upgrade subcommand", func(t *testing.T) {
305+
bufOut := new(bytes.Buffer)
306+
called := false
307+
308+
rootCmd := CreateRootCommand("v0.0.0-test", "")
309+
rootCmd.SetArgs([]string{"self", "upgrade"})
310+
rootCmd.SetOut(bufOut)
311+
rootCmd.SetErr(bufOut)
312+
selfCmd := &cobra.Command{
313+
Use: "self",
314+
Short: "Manage lets CLI itself",
315+
}
316+
rootCmd.AddCommand(selfCmd)
317+
318+
selfCmd.AddCommand(initUpgradeCommandWith(func() (upgrade.Upgrader, error) {
319+
return mockUpgraderFunc(func(ctx context.Context) error {
320+
called = true
321+
322+
return nil
323+
}), nil
324+
}))
325+
326+
err := rootCmd.Execute()
327+
if err != nil {
328+
t.Fatalf("unexpected error: %v", err)
329+
}
330+
331+
if !called {
332+
t.Fatal("expected upgrader to be called")
333+
}
334+
})
335+
336+
t.Run("should return upgrader error for self upgrade command", func(t *testing.T) {
337+
bufOut := new(bytes.Buffer)
338+
339+
rootCmd := CreateRootCommand("v0.0.0-test", "")
340+
rootCmd.SetArgs([]string{"self", "upgrade"})
341+
rootCmd.SetOut(bufOut)
342+
rootCmd.SetErr(bufOut)
343+
selfCmd := &cobra.Command{
344+
Use: "self",
345+
Short: "Manage lets CLI itself",
346+
}
347+
rootCmd.AddCommand(selfCmd)
348+
349+
selfCmd.AddCommand(initUpgradeCommandWith(func() (upgrade.Upgrader, error) {
350+
return mockUpgraderFunc(func(ctx context.Context) error {
351+
return errors.New("upgrade failed")
352+
}), nil
353+
}))
354+
355+
err := rootCmd.Execute()
356+
if err == nil {
357+
t.Fatal("expected upgrader error")
358+
}
359+
360+
if !strings.Contains(err.Error(), "can not self-upgrade binary") {
361+
t.Fatalf("expected self-upgrade error, got %q", err.Error())
362+
}
363+
})
364+
}
365+
366+
type mockUpgraderFunc func(context.Context) error
367+
368+
func (f mockUpgraderFunc) Upgrade(ctx context.Context) error {
369+
return f(ctx)
301370
}

internal/cmd/self.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ func initSelfCmd(rootCmd *cobra.Command, version string, openURL func(string) er
2626

2727
selfCmd.AddCommand(initDocCommand(openURL))
2828
selfCmd.AddCommand(initLspCommand(version))
29+
selfCmd.AddCommand(initUpgradeCommand(version))
2930
}

internal/cmd/upgrade.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/lets-cli/lets/internal/upgrade"
7+
"github.com/lets-cli/lets/internal/upgrade/registry"
8+
"github.com/spf13/cobra"
9+
)
10+
11+
type upgraderFactory func() (upgrade.Upgrader, error)
12+
13+
func initUpgradeCommand(version string) *cobra.Command {
14+
return initUpgradeCommandWith(func() (upgrade.Upgrader, error) {
15+
return upgrade.NewBinaryUpgrader(registry.NewGithubRegistry(), version)
16+
})
17+
}
18+
19+
func initUpgradeCommandWith(createUpgrader upgraderFactory) *cobra.Command {
20+
upgradeCmd := &cobra.Command{
21+
Use: "upgrade",
22+
Short: "Upgrade lets to latest version",
23+
Args: cobra.NoArgs,
24+
RunE: func(cmd *cobra.Command, args []string) error {
25+
upgrader, err := createUpgrader()
26+
if err != nil {
27+
return fmt.Errorf("can not self-upgrade binary: %w", err)
28+
}
29+
30+
if err := upgrader.Upgrade(cmd.Context()); err != nil {
31+
return fmt.Errorf("can not self-upgrade binary: %w", err)
32+
}
33+
34+
return nil
35+
},
36+
}
37+
38+
return upgradeCmd
39+
}

0 commit comments

Comments
 (0)