Skip to content

Commit 38f9d33

Browse files
committed
refactor: significantly refactor builds viewer
1 parent 124360a commit 38f9d33

File tree

17 files changed

+1368
-1355
lines changed

17 files changed

+1368
-1355
lines changed

pkg/cmd/build.go

Lines changed: 42 additions & 424 deletions
Large diffs are not rendered by default.

pkg/cmd/buildtargetoutput.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88

99
"github.com/stainless-api/stainless-api-cli/pkg/console"
10+
"github.com/stainless-api/stainless-api-cli/pkg/components/build"
1011

1112
"github.com/stainless-api/stainless-api-cli/pkg/jsonflag"
1213
"github.com/stainless-api/stainless-api-go"
@@ -106,7 +107,7 @@ func handleBuildsTargetOutputsRetrieve(ctx context.Context, cmd *cli.Command) er
106107

107108
group := console.Info("Downloading output")
108109
if cmd.Bool("pull") {
109-
return pullOutput(res.Output, res.URL, res.Ref, "", &group)
110+
return build.PullOutput(res.Output, res.URL, res.Ref, "", &group)
110111
}
111112

112113
return nil

pkg/cmd/dev.go

Lines changed: 26 additions & 259 deletions
Original file line numberDiff line numberDiff line change
@@ -12,262 +12,17 @@ import (
1212
"strings"
1313
"time"
1414

15-
"github.com/charmbracelet/bubbles/help"
16-
"github.com/charmbracelet/bubbles/key"
1715
tea "github.com/charmbracelet/bubbletea"
1816
"github.com/charmbracelet/huh"
17+
"github.com/stainless-api/stainless-api-cli/pkg/components/build"
18+
"github.com/stainless-api/stainless-api-cli/pkg/components/dev"
1919
"github.com/stainless-api/stainless-api-cli/pkg/console"
20-
"github.com/stainless-api/stainless-api-cli/pkg/stainlessutils"
21-
"github.com/stainless-api/stainless-api-cli/pkg/stainlessviews"
2220
"github.com/stainless-api/stainless-api-go"
2321
"github.com/stainless-api/stainless-api-go/option"
2422
"github.com/tidwall/gjson"
2523
"github.com/urfave/cli/v3"
2624
)
2725

28-
var ErrUserCancelled = errors.New("user cancelled")
29-
30-
// BuildModel represents the bubbletea model for build monitoring
31-
type BuildModel struct {
32-
start func() (*stainless.Build, error)
33-
started time.Time
34-
ended *time.Time
35-
build *stainless.Build
36-
branch string
37-
help help.Model
38-
diagnostics []stainless.BuildDiagnostic
39-
downloads map[stainless.Target]stainlessviews.DownloadStatus
40-
view string
41-
42-
cc *apiCommandContext
43-
ctx context.Context
44-
err error
45-
isCompleted bool
46-
}
47-
48-
type tickMsg time.Time
49-
type fetchBuildMsg *stainless.Build
50-
type fetchDiagnosticsMsg []stainless.BuildDiagnostic
51-
type errorMsg error
52-
type downloadMsg stainless.Target
53-
type fileChangeMsg struct{}
54-
55-
func NewBuildModel(cc *apiCommandContext, ctx context.Context, branch string, fn func() (*stainless.Build, error)) BuildModel {
56-
return BuildModel{
57-
start: fn,
58-
started: time.Now(),
59-
cc: cc,
60-
ctx: ctx,
61-
branch: branch,
62-
help: help.New(),
63-
}
64-
}
65-
66-
func (m BuildModel) Init() tea.Cmd {
67-
return tea.Batch(
68-
tea.Tick(time.Second, func(t time.Time) tea.Msg {
69-
return tickMsg(t)
70-
}),
71-
func() tea.Msg {
72-
build, err := m.start()
73-
if err != nil {
74-
return errorMsg(err)
75-
}
76-
return fetchBuildMsg(build)
77-
},
78-
)
79-
}
80-
81-
func (m BuildModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
82-
cmds := []tea.Cmd{}
83-
switch msg := msg.(type) {
84-
case tea.WindowSizeMsg:
85-
m.help.Width = msg.Width
86-
case tea.KeyMsg:
87-
switch msg.String() {
88-
case "ctrl+c":
89-
m.err = ErrUserCancelled
90-
cmds = append(cmds, tea.Quit)
91-
case "enter":
92-
if m.cc.cmd.Bool("watch") {
93-
cmds = append(cmds, tea.Quit)
94-
}
95-
}
96-
97-
case downloadMsg:
98-
download := m.downloads[stainless.Target(msg)]
99-
download.Status = "completed"
100-
m.downloads[stainless.Target(msg)] = download
101-
102-
case tickMsg:
103-
if m.build != nil {
104-
cmds = append(cmds, m.fetchBuildStatus())
105-
}
106-
m.getBuildDuration()
107-
cmds = append(cmds, tea.Tick(time.Second, func(t time.Time) tea.Msg {
108-
return tickMsg(t)
109-
}))
110-
111-
case fetchBuildMsg:
112-
if m.build == nil {
113-
m.build = msg
114-
m.downloads = make(map[stainless.Target]stainlessviews.DownloadStatus)
115-
for targetName, targetConfig := range m.cc.workspaceConfig.Targets {
116-
m.downloads[stainless.Target(targetName)] = stainlessviews.DownloadStatus{
117-
Status: "not started",
118-
Path: targetConfig.OutputPath,
119-
}
120-
}
121-
cmds = append(cmds, m.updateView("header"))
122-
}
123-
124-
m.build = msg
125-
buildObj := stainlessutils.NewBuild(m.build)
126-
if !m.isCompleted {
127-
// Check if all commit steps are completed
128-
allCommitsCompleted := true
129-
for _, target := range buildObj.Languages() {
130-
buildTarget := buildObj.BuildTarget(target)
131-
if buildTarget != nil && !buildTarget.IsCommitCompleted() {
132-
allCommitsCompleted = false
133-
break
134-
}
135-
}
136-
if allCommitsCompleted {
137-
m.isCompleted = true
138-
cmds = append(cmds, m.fetchDiagnostics())
139-
}
140-
}
141-
languages := buildObj.Languages()
142-
for _, target := range languages {
143-
buildTarget := buildObj.BuildTarget(target)
144-
if buildTarget == nil {
145-
continue
146-
}
147-
status, _, conclusion := buildTarget.StepInfo("commit")
148-
if status == "completed" && conclusion != "fatal" {
149-
if download, ok := m.downloads[target]; ok && download.Status == "not started" {
150-
download.Status = "started"
151-
cmds = append(cmds, m.downloadTarget(target))
152-
m.downloads[target] = download
153-
}
154-
}
155-
}
156-
157-
case fetchDiagnosticsMsg:
158-
if m.diagnostics == nil {
159-
m.diagnostics = msg
160-
cmds = append(cmds, m.updateView("diagnostics"))
161-
}
162-
163-
case errorMsg:
164-
m.err = msg
165-
cmds = append(cmds, tea.Quit)
166-
167-
case fileChangeMsg:
168-
// File change detected, exit with success
169-
cmds = append(cmds, tea.Quit)
170-
}
171-
return m, tea.Sequence(cmds...)
172-
}
173-
174-
func (m BuildModel) ShortHelp() []key.Binding {
175-
if m.cc.cmd.Bool("watch") {
176-
return []key.Binding{
177-
key.NewBinding(key.WithKeys("ctrl+c"), key.WithHelp("ctrl-c", "quit")),
178-
key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "rebuild")),
179-
}
180-
} else {
181-
return []key.Binding{key.NewBinding(key.WithKeys("ctrl+c"), key.WithHelp("ctrl-c", "quit"))}
182-
}
183-
}
184-
185-
func (m BuildModel) FullHelp() [][]key.Binding {
186-
if m.cc.cmd.Bool("watch") {
187-
return [][]key.Binding{{
188-
key.NewBinding(key.WithKeys("ctrl+c"), key.WithHelp("ctrl-c", "quit")),
189-
key.NewBinding(key.WithKeys("enter"), key.WithHelp("enter", "rebuild")),
190-
}}
191-
} else {
192-
return [][]key.Binding{{key.NewBinding(key.WithKeys("ctrl+c"), key.WithHelp("ctrl-c", "quit"))}}
193-
}
194-
}
195-
196-
func (m BuildModel) downloadTarget(target stainless.Target) tea.Cmd {
197-
return func() tea.Msg {
198-
if m.build == nil {
199-
return errorMsg(fmt.Errorf("no current build to download target from"))
200-
}
201-
params := stainless.BuildTargetOutputGetParams{
202-
BuildID: m.build.ID,
203-
Target: stainless.BuildTargetOutputGetParamsTarget(target),
204-
Type: "source",
205-
Output: "git",
206-
}
207-
outputRes, err := m.cc.client.Builds.TargetOutputs.Get(
208-
context.TODO(),
209-
params,
210-
)
211-
if err != nil {
212-
return errorMsg(err)
213-
}
214-
err = pullOutput(outputRes.Output, outputRes.URL, outputRes.Ref, m.downloads[target].Path, &console.Group{})
215-
if err != nil {
216-
return errorMsg(err)
217-
}
218-
return downloadMsg(target)
219-
}
220-
}
221-
222-
func (m BuildModel) fetchBuildStatus() tea.Cmd {
223-
return func() tea.Msg {
224-
if m.build == nil {
225-
return errorMsg(fmt.Errorf("no current build to fetch status for"))
226-
}
227-
build, err := m.cc.client.Builds.Get(m.ctx, m.build.ID)
228-
if err != nil {
229-
return errorMsg(fmt.Errorf("failed to get build status: %v", err))
230-
}
231-
return fetchBuildMsg(build)
232-
}
233-
}
234-
235-
func (m BuildModel) fetchDiagnostics() tea.Cmd {
236-
return func() tea.Msg {
237-
if m.build == nil {
238-
return errorMsg(fmt.Errorf("no current build to fetch diagnostics for"))
239-
}
240-
diags := []stainless.BuildDiagnostic{}
241-
diagnostics := m.cc.client.Builds.Diagnostics.ListAutoPaging(m.ctx, m.build.ID, stainless.BuildDiagnosticListParams{
242-
Limit: stainless.Float(100),
243-
})
244-
for diagnostics.Next() {
245-
diag := diagnostics.Current()
246-
if !diag.Ignored {
247-
diags = append(diags, diag)
248-
}
249-
}
250-
return fetchDiagnosticsMsg(diags)
251-
}
252-
}
253-
254-
func (m *BuildModel) getBuildDuration() time.Duration {
255-
if m.build == nil {
256-
return time.Since(m.started)
257-
}
258-
259-
buildObj := stainlessutils.NewBuild(m.build)
260-
if buildObj.IsCompleted() {
261-
if m.ended == nil {
262-
now := time.Now()
263-
m.ended = &now
264-
}
265-
return m.ended.Sub(m.started)
266-
}
267-
268-
return time.Since(m.started)
269-
}
270-
27126
var devCommand = cli.Command{
27227
Name: "preview",
27328
Aliases: []string{"dev"},
@@ -340,7 +95,7 @@ func runPreview(ctx context.Context, cmd *cli.Command) error {
34095
if cmd.IsSet("target") {
34196
selectedTargets = cmd.StringSlice("target")
34297
for _, target := range selectedTargets {
343-
if !isValidTarget(targetInfos, target) {
98+
if !isValidTarget(targetInfos, stainless.Target(target)) {
34499
return fmt.Errorf("invalid language target: %s", target)
345100
}
346101
}
@@ -364,15 +119,15 @@ func runPreview(ctx context.Context, cmd *cli.Command) error {
364119
for {
365120
// Make the user get past linter errors
366121
if err := runLinter(ctx, cmd, true); err != nil {
367-
if errors.Is(err, ErrUserCancelled) {
122+
if errors.Is(err, build.ErrUserCancelled) {
368123
return nil
369124
}
370125
return err
371126
}
372127

373128
// Start the build process
374129
if err := runDevBuild(ctx, cc, cmd, selectedBranch, targets); err != nil {
375-
if errors.Is(err, ErrUserCancelled) {
130+
if errors.Is(err, build.ErrUserCancelled) {
376131
return nil
377132
}
378133
return err
@@ -464,22 +219,34 @@ func runDevBuild(ctx context.Context, cc *apiCommandContext, cmd *cli.Command, b
464219
AllowEmpty: stainless.Bool(true),
465220
}
466221

467-
model := NewBuildModel(cc, ctx, branch, func() (*stainless.Build, error) {
468-
build, err := cc.client.Builds.New(ctx, buildReq, option.WithMiddleware(cc.AsMiddleware()))
469-
if err != nil {
470-
return nil, fmt.Errorf("failed to create build: %v", err)
471-
}
472-
return build, err
473-
})
222+
downloads := make(map[stainless.Target]string)
223+
for targetName, targetConfig := range cc.workspaceConfig.Targets {
224+
downloads[stainless.Target(targetName)] = targetConfig.OutputPath
225+
}
226+
227+
model := dev.NewModel(
228+
cc.client,
229+
ctx,
230+
branch,
231+
func() (*stainless.Build, error) {
232+
build, err := cc.client.Builds.New(ctx, buildReq, option.WithMiddleware(cc.AsMiddleware()))
233+
if err != nil {
234+
return nil, fmt.Errorf("failed to create build: %v", err)
235+
}
236+
return build, err
237+
},
238+
downloads,
239+
cmd.Bool("watch"),
240+
)
474241

475242
p := tea.NewProgram(model)
476243
finalModel, err := p.Run()
477244

478245
if err != nil {
479246
return fmt.Errorf("failed to run TUI: %v", err)
480247
}
481-
if buildModel, ok := finalModel.(BuildModel); ok {
482-
return buildModel.err
248+
if buildModel, ok := finalModel.(dev.Model); ok {
249+
return buildModel.Err
483250
}
484251
return nil
485252
}

pkg/cmd/dev_view.go

Lines changed: 0 additions & 45 deletions
This file was deleted.

0 commit comments

Comments
 (0)