Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c465748
Treat empty strings as unset
Mar 16, 2026
0dd1d04
Merge pull request #529 from stainless-sdks/bruce/fix-auth-tests
bruce-hill Mar 16, 2026
64c31cf
fix: only set client options when the corresponding CLI flag or env v…
stainless-app[bot] Mar 17, 2026
d6f4985
chore(internal): version bump
stainless-app[bot] Mar 16, 2026
23b8227
fix: fill project property more uniformly
yjp20 Mar 18, 2026
73a12cd
refactor: auto-set Before hook on all subcommands via traversal
yjp20 Mar 18, 2026
7427170
feat: add git Show and CurrentBranch helpers
yjp20 Mar 13, 2026
c094b0f
feat(components/diagnostics): rewrite diagnostics view with Rust-styl…
yjp20 Mar 13, 2026
270e85b
feat(components/build): rewrite build pipeline view as single-line wi…
yjp20 Mar 13, 2026
b7cc41a
feat(cmd/dev): refactor dev command to use compare builds
yjp20 Mar 13, 2026
0c0c4ee
feat(cmd/build): use build component view for builds list
yjp20 Mar 13, 2026
407ce31
feat(cmd/builddiagnostic): use diagnostics component for diagnostics …
yjp20 Mar 13, 2026
e16abc3
fix: read check step conclusion from top-level field
yjp20 Mar 16, 2026
83b7995
feat(components/dev): remove config section and waiting message from …
yjp20 Mar 16, 2026
cba8ce9
feat(components/build): single newline after header, show download path
yjp20 Mar 16, 2026
63df786
feat(cmd/build): add spacing between header and targets in builds list
yjp20 Mar 16, 2026
fcb4d88
fix(components/dev): preserve preview content on quit
yjp20 Mar 16, 2026
397114c
refactor: don't use deprecated .Completed property
yjp20 Mar 18, 2026
31a38dc
fix: make download error display better
yjp20 Mar 18, 2026
a057170
chore: automatically generate demo gifs
yjp20 Mar 16, 2026
44a8a3a
refactor(cmd/lint): move getDiagnostics to cmd/lint.go
yjp20 Mar 19, 2026
48c573e
refactor(cmd/lint): remove unused canSkip logic
yjp20 Mar 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added assets/auth-login.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 18 additions & 0 deletions assets/auth-login.tape
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Output assets/auth-login.gif
Set Shell "bash"
Set FontSize 14
Set Width 900
Set Height 300
Set Theme "Catppuccin Mocha"
Set Padding 20

Type "stl auth login"
Sleep 500ms
Enter
Sleep 2s

# Accept "Open browser?" confirm
Enter
Sleep 3s

Sleep 1s
Binary file added assets/build:diagnostics-list.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions assets/builds-diagnostics-list.tape
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Output "assets/builds-diagnostics-list.gif"
Set Shell "bash"
Set FontSize 14
Set Width 1200
Set Height 600
Set Theme "Catppuccin Mocha"
Set Padding 20

Type "stl builds:diagnostics list --build-id bui_0cmmtsksxj000425s640c55yf1"
Sleep 500ms
Enter
Sleep 3s

Sleep 1s
Binary file added assets/builds-list.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions assets/builds-list.tape
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Output assets/builds-list.gif
Set Shell "bash"
Set FontSize 14
Set Width 1200
Set Height 800
Set Theme "Catppuccin Mocha"
Set Padding 20

Type "stl builds list --project acme-api --max-items 3"
Sleep 500ms
Enter
Sleep 3s

Sleep 1s
Binary file added assets/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 48 additions & 0 deletions assets/demo.tape
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
Output assets/demo.gif
Set Shell "bash"
Set FontSize 14
Set Width 1200
Set Height 800
Set Theme "Catppuccin Mocha"
Set Padding 20

# Init
Type "stl init"
Sleep 500ms
Enter
Sleep 2s

# Accept "Open browser?" confirm
Enter
Sleep 3s


# Select project: Down to "acme-api", then Enter
Down
Sleep 300ms
Enter
Sleep 2s

# Accept openapi spec path default
Enter
Sleep 1s

# Accept stainless config path default
Enter
Sleep 2s

# Accept target output paths (one Enter per target)
Enter
Sleep 500ms
Enter
Sleep 500ms
Enter
Sleep 8s

# Preview
Type "stl preview"
Sleep 500ms
Enter
Sleep 15s
Ctrl+C
Sleep 1s
Binary file added assets/preview.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions assets/preview.tape
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Output assets/preview.gif
Set Shell "bash"
Set FontSize 14
Set Width 1200
Set Height 600
Set Theme "Catppuccin Mocha"
Set Padding 20

Hide
Type "stl preview --project acme-api --oas ./openapi.yml --config ./stainless.yml"
Show
Sleep 1.5s
Enter
Sleep 15s
Ctrl+C
Sleep 1s
28 changes: 28 additions & 0 deletions internal/cmd/mock-server/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package main

import (
"flag"
"fmt"
"net/http"

"github.com/stainless-api/stainless-api-cli/internal/mockstainless"
)

func main() {
port := flag.Int("port", 4010, "port to listen on")
flag.Parse()

mock := mockstainless.NewMock(
mockstainless.WithDefaultOrg(),
mockstainless.WithDefaultProject(),
mockstainless.WithDefaultCompareBuild(),
mockstainless.WithDeviceAuth(1),
mockstainless.WithGitRepos(),
)
defer mock.Cleanup()
addr := fmt.Sprintf(":%d", *port)
fmt.Printf("Mock server listening on %s\n", addr)
if err := http.ListenAndServe(addr, mock.Server()); err != nil {
fmt.Printf("Server error: %v\n", err)
}
}
201 changes: 201 additions & 0 deletions internal/mockstainless/builders.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package mockstainless

import "time"

// --- CheckStep builders ---

func CheckStepNotStarted() M {
return M{"status": "not_started"}
}

func CheckStepInProgress() M {
return M{"status": "in_progress", "url": ""}
}

func CheckStepCompleted(conclusion string, opts ...func(M)) M {
m := M{"status": "completed", "conclusion": conclusion, "url": ""}
for _, opt := range opts {
opt(m)
}
return m
}

// --- Commit builders ---

func CommitNotStarted() M {
return M{"status": "not_started"}
}

func CommitInProgress() M {
return M{"status": "in_progress"}
}

func CommitCompleted(conclusion string, opts ...func(M)) M {
m := M{
"status": "completed",
"conclusion": conclusion,
}
for _, opt := range opts {
opt(m)
}
return m
}

func WithCommitData(owner, repo, sha string, additions, deletions int) func(M) {
return func(m M) {
m["commit"] = M{
"sha": sha,
"tree_oid": "tree_" + sha[:7],
"repo": M{
"owner": owner,
"name": repo,
},
"stats": M{
"additions": additions,
"deletions": deletions,
"total": additions + deletions,
},
}
}
}

func WithMergeConflictPR(owner, repo string, number int) func(M) {
return func(m M) {
m["merge_conflict_pr"] = M{
"repo": M{
"owner": owner,
"name": repo,
},
"number": number,
}
}
}

// --- BuildTarget builders ---

type TargetOption func(M)

// Target creates a build target with the given status and commit state.
// Lint, build, and test default to not_started.
func Target(status string, commit M, opts ...TargetOption) M {
m := M{
"object": "build_target",
"status": status,
"install_url": "",
"commit": commit,
}
for _, opt := range opts {
opt(m)
}
return m
}

func WithLint(step M) TargetOption { return func(m M) { m["lint"] = step } }
func WithBuild(step M) TargetOption { return func(m M) { m["build"] = step } }
func WithTest(step M) TargetOption { return func(m M) { m["test"] = step } }

// Convenience target constructors

func CompletedTarget(owner, repo, sha string, additions, deletions int) M {
return Target("completed",
CommitCompleted("success", WithCommitData(owner, repo, sha, additions, deletions)),
WithLint(CheckStepCompleted("success")),
WithBuild(CheckStepCompleted("success")),
WithTest(CheckStepCompleted("success")),
)
}

func WarningTarget(owner, repo, sha string, additions, deletions int) M {
return Target("completed",
CommitCompleted("warning", WithCommitData(owner, repo, sha, additions, deletions)),
WithLint(CheckStepCompleted("success")),
WithBuild(CheckStepCompleted("success")),
WithTest(CheckStepCompleted("success")),
)
}

func ErrorTarget(owner, repo, sha string, additions, deletions int) M {
return Target("completed",
CommitCompleted("error", WithCommitData(owner, repo, sha, additions, deletions)),
WithLint(CheckStepCompleted("success")),
WithBuild(CheckStepCompleted("success")),
WithTest(CheckStepCompleted("failure")),
)
}

func FatalTarget() M {
return Target("completed", CommitCompleted("fatal"))
}

func MergeConflictTarget(owner, repo string, prNum int) M {
return Target("completed",
CommitCompleted("merge_conflict", WithMergeConflictPR(owner, repo, prNum)),
)
}

func NotStartedTarget() M {
return Target("not_started", CommitNotStarted())
}

func InProgressTarget() M {
return Target("codegen", CommitInProgress())
}

// --- Build builders ---

type BuildOption func(M)

// Build creates a build with sensible defaults.
func Build(id string, opts ...BuildOption) M {
m := M{
"id": id,
"config_commit": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0",
"created_at": time.Now().Format(time.RFC3339),
"org": DefaultOrg,
"project": DefaultProject,
"targets": M{},
}
for _, opt := range opts {
opt(m)
}
return m
}

func WithTarget(name string, target M) BuildOption {
return func(m M) {
targets := m["targets"].(M)
targets[name] = target
}
}

func WithCreatedAt(t time.Time) BuildOption {
return func(m M) { m["created_at"] = t.Format(time.RFC3339) }
}

func WithConfigCommit(sha string) BuildOption {
return func(m M) { m["config_commit"] = sha }
}

// --- Diagnostic builders ---

type DiagnosticOption func(M)

func Diagnostic(code, level, message string, opts ...DiagnosticOption) M {
m := M{
"code": code,
"level": level,
"message": message,
"ignored": false,
"more": nil,
}
for _, opt := range opts {
opt(m)
}
return m
}

func WithOASRef(ref string) DiagnosticOption { return func(m M) { m["oas_ref"] = ref } }
func WithConfigRef(ref string) DiagnosticOption { return func(m M) { m["config_ref"] = ref } }
func WithMore(markdown string) DiagnosticOption {
return func(m M) { m["more"] = M{"type": "markdown", "markdown": markdown} }
}
Loading
Loading