Skip to content

Commit ca27764

Browse files
committed
feat(init/builds create): use dev component instead
1 parent 3148251 commit ca27764

4 files changed

Lines changed: 121 additions & 97 deletions

File tree

pkg/cmd/build.go

Lines changed: 22 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ import (
1111
"path"
1212
"strings"
1313

14-
tea "github.com/charmbracelet/bubbletea"
1514
"github.com/stainless-api/stainless-api-cli/internal/apiquery"
1615
"github.com/stainless-api/stainless-api-cli/internal/requestflag"
1716
cbuild "github.com/stainless-api/stainless-api-cli/pkg/components/build"
17+
cdev "github.com/stainless-api/stainless-api-cli/pkg/components/dev"
1818
"github.com/stainless-api/stainless-api-cli/pkg/console"
1919
"github.com/stainless-api/stainless-api-cli/pkg/stainlessutils"
2020
"github.com/stainless-api/stainless-api-cli/pkg/workspace"
@@ -25,24 +25,15 @@ import (
2525
"github.com/urfave/cli/v3"
2626
)
2727

28-
// WaitMode represents the level of waiting for build completion
29-
type WaitMode int
30-
31-
const (
32-
WaitNone WaitMode = iota // Don't wait
33-
WaitCommit // Wait for commit only
34-
WaitAll // Wait for everything including workflows
35-
)
36-
3728
// parseWaitMode converts the --wait flag string to a WaitMode
38-
func parseWaitMode(wait string) (WaitMode, error) {
29+
func parseWaitMode(wait string) (cdev.WaitMode, error) {
3930
switch wait {
4031
case "none", "false": // Accept both "none" and "false" for backwards compatibility
41-
return WaitNone, nil
32+
return cdev.WaitNone, nil
4233
case "commit":
43-
return WaitCommit, nil
34+
return cdev.WaitCommit, nil
4435
case "all":
45-
return WaitAll, nil
36+
return cdev.WaitAll, nil
4637
default:
4738
return 0, fmt.Errorf("invalid --wait value: %q (must be 'none', 'commit', or 'all')", wait)
4839
}
@@ -429,20 +420,26 @@ func handleBuildsCreate(ctx context.Context, cmd *cli.Command) error {
429420

430421
buildGroup.Property("build_id", build.ID)
431422

432-
if waitMode > WaitNone {
423+
if waitMode > cdev.WaitNone {
433424
console.Spacer()
434-
buildModel := cbuild.NewModel(client, ctx, *build, cmd.String("branch"), downloadPaths)
435-
buildModel.CommitOnly = waitMode == WaitCommit
436-
model := tea.Model(buildCompletionModel{
437-
Build: buildModel,
438-
WaitMode: waitMode,
425+
devModel := cdev.NewModel(cdev.ModelConfig{
426+
Client: client,
427+
Ctx: ctx,
428+
Branch: cmd.String("branch"),
429+
Start: func() (*stainless.Build, error) { return build, nil },
430+
DownloadPaths: downloadPaths,
431+
Label: "BUILD",
432+
WaitMode: waitMode,
433+
Indent: " ",
439434
})
440-
model, err = console.NewProgram(model).Run()
435+
devModel.Build.CommitOnly = waitMode == cdev.WaitCommit
436+
model, err := console.NewProgram(devModel).Run()
441437
if err != nil {
442438
console.Warn("%s", err.Error())
443439
}
444-
b := model.(buildCompletionModel).Build
445-
build = &b.Build
440+
if m, ok := model.(cdev.Model); ok {
441+
build = &m.Build.Build
442+
}
446443
console.Spacer()
447444
}
448445

@@ -454,7 +451,7 @@ func handleBuildsCreate(ctx context.Context, cmd *cli.Command) error {
454451
}
455452

456453
// Only check for failures if we waited for the build
457-
if waitMode == WaitNone {
454+
if waitMode == cdev.WaitNone {
458455
return nil
459456
}
460457

@@ -475,7 +472,7 @@ func handleBuildsCreate(ctx context.Context, cmd *cli.Command) error {
475472
}
476473

477474
// Only check workflow failures if we waited for them
478-
if waitMode >= WaitAll {
475+
if waitMode >= cdev.WaitAll {
479476
if bt.Lint.Conclusion == "failure" || bt.Test.Conclusion == "failure" || bt.Build.Conclusion == "failure" {
480477
failures = append(failures, fmt.Errorf("%s workflow failed", target))
481478
}
@@ -489,65 +486,6 @@ func handleBuildsCreate(ctx context.Context, cmd *cli.Command) error {
489486
return nil
490487
}
491488

492-
type buildCompletionModel struct {
493-
Build cbuild.Model
494-
WaitMode WaitMode
495-
}
496-
497-
func (c buildCompletionModel) Init() tea.Cmd {
498-
return c.Build.Init()
499-
}
500-
501-
func (c buildCompletionModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
502-
var cmd tea.Cmd
503-
c.Build, cmd = c.Build.Update(msg)
504-
505-
if c.IsCompleted() {
506-
return c, tea.Sequence(
507-
cmd,
508-
tea.Quit,
509-
)
510-
}
511-
512-
return c, cmd
513-
}
514-
515-
func (c buildCompletionModel) IsCompleted() bool {
516-
b := stainlessutils.NewBuild(c.Build.Build)
517-
for _, target := range b.Languages() {
518-
buildTarget := b.BuildTarget(target)
519-
520-
if buildTarget == nil {
521-
return false
522-
}
523-
524-
// Check if download is completed (if applicable)
525-
downloadIsCompleted := true
526-
if buildTarget.IsCommitCompleted() && buildTarget.IsGoodCommitConclusion() {
527-
if download, ok := c.Build.Downloads[target]; ok {
528-
downloadIsCompleted = download.Status == "completed"
529-
}
530-
}
531-
532-
// Check if target is done based on wait mode
533-
done := buildTarget.IsCommitCompleted()
534-
if c.WaitMode >= WaitAll {
535-
done = buildTarget.IsCompleted()
536-
}
537-
538-
if !done || !downloadIsCompleted {
539-
return false
540-
}
541-
}
542-
543-
return true
544-
}
545-
546-
func (c buildCompletionModel) View() string {
547-
c.Build.CommitOnly = c.WaitMode == WaitCommit
548-
return c.Build.View()
549-
}
550-
551489
func handleBuildsRetrieve(ctx context.Context, cmd *cli.Command) error {
552490
client := stainless.NewClient(getDefaultRequestOptions(cmd)...)
553491
unusedArgs := cmd.Args().Slice()

pkg/cmd/init.go

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313
"strings"
1414
"time"
1515

16-
cbuild "github.com/stainless-api/stainless-api-cli/pkg/components/build"
16+
cdev "github.com/stainless-api/stainless-api-cli/pkg/components/dev"
1717
"github.com/stainless-api/stainless-api-cli/pkg/console"
1818
"github.com/stainless-api/stainless-api-cli/pkg/stainlessutils"
1919
"github.com/stainless-api/stainless-api-cli/pkg/workspace"
@@ -483,8 +483,6 @@ func initializeWorkspace(ctx context.Context, cmd *cli.Command, client stainless
483483

484484
console.Spacer()
485485

486-
console.Info("Waiting for build to complete...")
487-
488486
// Try to get the latest build for this project (which should have been created automatically)
489487
build, err := getLatestBuild(ctx, client, projectSlug, "main")
490488
if err != nil {
@@ -496,14 +494,20 @@ func initializeWorkspace(ctx context.Context, cmd *cli.Command, client stainless
496494
downloadPaths[stainless.Target(targetName)] = targetConfig.OutputPath
497495
}
498496

499-
buildModel := cbuild.NewModel(client, ctx, *build, "main", downloadPaths)
500-
buildModel.CommitOnly = true
501-
model := buildCompletionModel{
502-
Build: buildModel,
503-
WaitMode: WaitCommit,
504-
}
497+
devModel := cdev.NewModel(cdev.ModelConfig{
498+
Client: client,
499+
Ctx: ctx,
500+
Branch: "main",
501+
Start: func() (*stainless.Build, error) { return build, nil },
502+
DownloadPaths: downloadPaths,
503+
Label: "BUILD",
504+
WaitMode: cdev.WaitCommit,
505+
Indent: " ",
506+
})
507+
devModel.Build.CommitOnly = true
508+
devModel.Diagnostics.WorkspaceConfig = config
505509

506-
_, err = console.NewProgram(model).Run()
510+
_, err = console.NewProgram(devModel).Run()
507511
if err != nil {
508512
console.Warn("%s", err.Error())
509513
}

pkg/components/dev/model.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ import (
1616

1717
var ErrUserCancelled = errors.New("user cancelled")
1818

19+
// WaitMode represents the level of waiting for build completion.
20+
type WaitMode int
21+
22+
const (
23+
WaitNone WaitMode = iota // Don't wait
24+
WaitCommit // Wait for commit only
25+
WaitAll // Wait for everything including workflows
26+
)
27+
1928
type Model struct {
2029
Err error
2130

@@ -26,6 +35,9 @@ type Model struct {
2635
start func() (*stainless.Build, error)
2736
Branch string
2837
view string
38+
label string
39+
waitMode WaitMode
40+
Indent string
2941

3042
// models
3143

@@ -44,15 +56,25 @@ type ModelConfig struct {
4456
Start func() (*stainless.Build, error)
4557
DownloadPaths map[stainless.Target]string
4658
Watch bool
59+
Label string // Header label, defaults to "PREVIEW"
60+
WaitMode WaitMode // When non-zero, auto-quits after diagnostics are fetched and build targets reach completion
61+
Indent string // Prefix for every non-empty output line (e.g. " ")
4762
}
4863

4964
func NewModel(cfg ModelConfig) Model {
65+
label := cfg.Label
66+
if label == "" {
67+
label = "PREVIEW"
68+
}
5069
return Model{
5170
start: cfg.Start,
5271
Client: cfg.Client,
5372
Ctx: cfg.Ctx,
5473
Branch: cfg.Branch,
5574
Watch: cfg.Watch,
75+
label: label,
76+
waitMode: cfg.WaitMode,
77+
Indent: cfg.Indent,
5678
Help: help.New(),
5779
Build: build.NewModel(cfg.Client, cfg.Ctx, stainless.Build{}, cfg.Branch, cfg.DownloadPaths),
5880
Diagnostics: diagnostics.NewModel(cfg.Client, cfg.Ctx, nil),
@@ -139,6 +161,12 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
139161
// File change detected, exit with success
140162
cmds = append(cmds, tea.Quit)
141163
}
164+
165+
// Auto-quit when WaitMode is set and build targets have reached completion
166+
if m.waitMode > WaitNone && m.diagnosticsFetched() && m.isComplete() {
167+
return m, tea.Sequence(tea.Batch(cmds...), tea.Quit)
168+
}
169+
142170
return m, tea.Batch(cmds...)
143171
}
144172

@@ -163,3 +191,37 @@ func (m Model) FullHelp() [][]key.Binding {
163191
return [][]key.Binding{{key.NewBinding(key.WithKeys("ctrl+c"), key.WithHelp("ctrl-c", "quit"))}}
164192
}
165193
}
194+
195+
func (m Model) diagnosticsFetched() bool {
196+
return m.Diagnostics.Diagnostics != nil || m.Diagnostics.Err != nil
197+
}
198+
199+
func (m Model) isComplete() bool {
200+
buildObj := stainlessutils.NewBuild(m.Build.Build)
201+
for _, target := range buildObj.Languages() {
202+
buildTarget := buildObj.BuildTarget(target)
203+
if buildTarget == nil {
204+
return false
205+
}
206+
207+
// Check if download is completed (if applicable)
208+
if buildTarget.IsCommitCompleted() && buildTarget.IsGoodCommitConclusion() {
209+
if download, ok := m.Build.Downloads[target]; ok {
210+
if download.Status != "completed" {
211+
return false
212+
}
213+
}
214+
}
215+
216+
// Check if target is done based on wait mode
217+
done := buildTarget.IsCommitCompleted()
218+
if m.waitMode >= WaitAll {
219+
done = buildTarget.IsCompleted()
220+
}
221+
222+
if !done {
223+
return false
224+
}
225+
}
226+
return true
227+
}

pkg/components/dev/view.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,21 @@ func (m Model) View() string {
3030
s.WriteString("\n" + m.Err.Error() + "\n")
3131
}
3232

33-
return s.String()
33+
return m.applyIndent(s.String())
34+
}
35+
36+
// applyIndent prefixes every non-empty line with m.Indent.
37+
func (m Model) applyIndent(s string) string {
38+
if m.Indent == "" {
39+
return s
40+
}
41+
lines := strings.Split(s, "\n")
42+
for i, line := range lines {
43+
if line != "" {
44+
lines[i] = m.Indent + line
45+
}
46+
}
47+
return strings.Join(lines, "\n")
3448
}
3549

3650
// ViewPart represents a single part of the build view
@@ -43,7 +57,7 @@ var parts = []ViewPart{
4357
{
4458
Name: "header",
4559
View: func(m *Model, s *strings.Builder) {
46-
s.WriteString(build.ViewHeader("PREVIEW", m.Build.Build))
60+
s.WriteString(build.ViewHeader(m.label, m.Build.Build))
4761
},
4862
},
4963
{
@@ -69,6 +83,9 @@ var parts = []ViewPart{
6983
{
7084
Name: "studio",
7185
View: func(m *Model, s *strings.Builder) {
86+
if !m.Watch {
87+
return
88+
}
7289
if m.Build.ID != "" {
7390
url := fmt.Sprintf("https://app.stainless.com/%s/%s/studio?branch=%s", m.Build.Org, m.Build.Project, m.Branch)
7491
s.WriteString("\n")
@@ -80,6 +97,9 @@ var parts = []ViewPart{
8097
{
8198
Name: "help",
8299
View: func(m *Model, s *strings.Builder) {
100+
if !m.Watch {
101+
return
102+
}
83103
s.WriteString("\n")
84104
s.WriteString(m.Help.View(m))
85105
},
@@ -100,7 +120,7 @@ func (m *Model) updateView(targetState string) tea.Cmd {
100120
return nil
101121
}
102122

103-
output := ViewBuildRange(m, m.view, targetState)
123+
output := m.applyIndent(ViewBuildRange(m, m.view, targetState))
104124

105125
// Update model state
106126
m.view = targetState

0 commit comments

Comments
 (0)