Skip to content

Commit 4f0b92a

Browse files
pieternclaude
andauthored
Migrate apps commands to new spinner API and add lint rule (#4377)
## Changes Converts `cmdio.Spinner()` calls to `cmdio.NewSpinner()` in cmd/apps/ directory, following the refactoring pattern from commit 887c3eb. These deprecated calls were introduced in PR #4369 and PR #4293. The new API from PR #4351 provides synchronous `Close()` that blocks until the spinner fully terminates, preventing races with log output. Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent b7058e7 commit 4f0b92a

File tree

5 files changed

+28
-10
lines changed

5 files changed

+28
-10
lines changed

cmd/apps/bundle_helpers.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,14 @@ func shouldWaitForCompletion(cmd *cobra.Command) bool {
148148
return !skipWait
149149
}
150150

151+
// spinnerInterface matches the interface provided by cmdio.NewSpinner.
152+
type spinnerInterface interface {
153+
Update(msg string)
154+
Close()
155+
}
156+
151157
// createAppProgressCallback creates a progress callback for app operations.
152-
func createAppProgressCallback(spinner chan<- string) func(*apps.App) {
158+
func createAppProgressCallback(spinner spinnerInterface) func(*apps.App) {
153159
return func(i *apps.App) {
154160
if i.ComputeStatus == nil {
155161
return
@@ -158,7 +164,7 @@ func createAppProgressCallback(spinner chan<- string) func(*apps.App) {
158164
if statusMessage == "" {
159165
statusMessage = fmt.Sprintf("current status: %s", i.ComputeStatus.State)
160166
}
161-
spinner <- statusMessage
167+
spinner.Update(statusMessage)
162168
}
163169
}
164170

cmd/apps/import.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -94,21 +94,21 @@ Examples:
9494
// If no app name provided, list apps and let user select
9595
if name == "" {
9696
// List all apps
97-
spinner := cmdio.Spinner(ctx)
98-
spinner <- "Loading available apps..."
97+
spinner := cmdio.NewSpinner(ctx)
98+
spinner.Update("Loading available apps...")
9999
allApps := w.Apps.List(ctx, apps.ListAppsRequest{})
100100

101101
// Collect all apps
102102
var appList []apps.App
103103
for allApps.HasNext(ctx) {
104104
app, err := allApps.Next(ctx)
105105
if err != nil {
106-
close(spinner)
106+
spinner.Close()
107107
return fmt.Errorf("failed to iterate apps: %w", err)
108108
}
109109
appList = append(appList, app)
110110
}
111-
close(spinner)
111+
spinner.Close()
112112

113113
if len(appList) == 0 {
114114
return errors.New("no apps found in workspace")

cmd/apps/start_bundle.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ func BundleStartOverrideWithWrapper(wrapError ErrorWrapper) func(*cobra.Command,
4545

4646
var appInfo *apps.App
4747
if shouldWaitForCompletion(cmd) {
48-
spinner := cmdio.Spinner(ctx)
48+
spinner := cmdio.NewSpinner(ctx)
4949
appInfo, err = wait.OnProgress(createAppProgressCallback(spinner)).GetWithTimeout(getWaitTimeout(cmd))
50-
close(spinner)
50+
spinner.Close()
5151
if err != nil {
5252
return wrapError(cmd, appName, err)
5353
}

cmd/apps/stop_bundle.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ func BundleStopOverrideWithWrapper(wrapError ErrorWrapper) func(*cobra.Command,
4545

4646
var appInfo *apps.App
4747
if shouldWaitForCompletion(cmd) {
48-
spinner := cmdio.Spinner(ctx)
48+
spinner := cmdio.NewSpinner(ctx)
4949
appInfo, err = wait.OnProgress(createAppProgressCallback(spinner)).GetWithTimeout(getWaitTimeout(cmd))
50-
close(spinner)
50+
spinner.Close()
5151
if err != nil {
5252
return wrapError(cmd, appName, err)
5353
}

libs/gorules/rule_cmdio_spinner.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package gorules
2+
3+
import "github.com/quasilyte/go-ruleguard/dsl"
4+
5+
// NoNewCmdioSpinner forbids new calls to cmdio.Spinner except in cmd/workspace/ and cmd/account/ directories.
6+
// This rule ensures that spinner usage is limited to auto-generated workspace and account commands.
7+
func NoNewCmdioSpinner(m dsl.Matcher) {
8+
m.Match(`cmdio.Spinner($*_)`).
9+
Where(!m.File().PkgPath.Matches(`.*/cmd/workspace/.*`) &&
10+
!m.File().PkgPath.Matches(`.*/cmd/account/.*`)).
11+
Report(`cmdio.Spinner is deprecated, no new call sites allowed. Use cmdio.NewSpinner() instead`)
12+
}

0 commit comments

Comments
 (0)