Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 12 additions & 2 deletions internal/container/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"os"
stdruntime "runtime"
"slices"
"strings"
"time"

"github.com/containerd/errdefs"
Expand Down Expand Up @@ -121,10 +122,10 @@ func Start(ctx context.Context, rt runtime.Runtime, sink output.Sink, opts Start
setups := map[config.EmulatorType]postStartSetupFunc{
config.EmulatorAWS: awsconfig.Setup,
}
return runPostStartSetups(ctx, sink, opts.Containers, interactive, opts.LocalStackHost, setups)
return runPostStartSetups(ctx, sink, opts.Containers, interactive, opts.LocalStackHost, opts.WebAppURL, setups)
}

func runPostStartSetups(ctx context.Context, sink output.Sink, containers []config.ContainerConfig, interactive bool, localStackHost string, setups map[config.EmulatorType]postStartSetupFunc) error {
func runPostStartSetups(ctx context.Context, sink output.Sink, containers []config.ContainerConfig, interactive bool, localStackHost, webAppURL string, setups map[config.EmulatorType]postStartSetupFunc) error {
// build ordered list of unique types, keeping the first container config for each
firstByType := map[config.EmulatorType]config.ContainerConfig{}
var uniqueEmulatorTypes []config.EmulatorType
Expand All @@ -143,11 +144,20 @@ func runPostStartSetups(ctx context.Context, sink output.Sink, containers []conf
if err := setup(ctx, sink, interactive, resolvedHost); err != nil {
return err
}
emitPostStartPointers(sink, resolvedHost, webAppURL)
}
}
return nil
}

func emitPostStartPointers(sink output.Sink, resolvedHost, webAppURL string) {
output.EmitSecondary(sink, fmt.Sprintf("• Endpoint: %s", resolvedHost))
if webAppURL != "" {
output.EmitSecondary(sink, fmt.Sprintf("• Web app: %s", strings.TrimRight(webAppURL, "/")))
}
output.EmitSecondary(sink, "> Tip: View emulator logs: lstk logs --follow")
}

func pullImages(ctx context.Context, rt runtime.Runtime, sink output.Sink, containers []runtime.ContainerConfig) error {
for _, c := range containers {
// Remove any existing stopped container with the same name
Expand Down
28 changes: 28 additions & 0 deletions internal/container/start_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package container

import (
"bytes"
"context"
"errors"
"io"
Expand Down Expand Up @@ -28,3 +29,30 @@ func TestStart_ReturnsEarlyIfRuntimeUnhealthy(t *testing.T) {
assert.Contains(t, err.Error(), "runtime not healthy")
assert.True(t, output.IsSilent(err), "error should be silent since it was already emitted")
}

func TestEmitPostStartPointers_WithWebApp(t *testing.T) {
var out bytes.Buffer
sink := output.NewPlainSink(&out)

emitPostStartPointers(sink, "localhost.localstack.cloud:4566", "https://app.localstack.cloud/")

assert.Equal(t, ""+
"• Endpoint: localhost.localstack.cloud:4566\n"+
"• Web app: https://app.localstack.cloud\n"+
"> Tip: View emulator logs: lstk logs --follow\n",
out.String(),
)
}

func TestEmitPostStartPointers_WithoutWebApp(t *testing.T) {
var out bytes.Buffer
sink := output.NewPlainSink(&out)

emitPostStartPointers(sink, "127.0.0.1:4566", "")

assert.Equal(t, ""+
"• Endpoint: 127.0.0.1:4566\n"+
"> Tip: View emulator logs: lstk logs --follow\n",
out.String(),
)
}
13 changes: 9 additions & 4 deletions internal/output/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ import "time"
type MessageSeverity int

const (
SeverityInfo MessageSeverity = iota
SeveritySuccess
SeverityNote
SeverityWarning
SeverityInfo MessageSeverity = iota
SeveritySuccess // positive outcome
SeverityNote // informational
SeverityWarning // cautionary
SeveritySecondary // subdued/decorative text
)

type MessageEvent struct {
Expand Down Expand Up @@ -158,6 +159,10 @@ func EmitWarning(sink Sink, text string) {
Emit(sink, MessageEvent{Severity: SeverityWarning, Text: text})
}

func EmitSecondary(sink Sink, text string) {
Emit(sink, MessageEvent{Severity: SeveritySecondary, Text: text})
}

func EmitStatus(sink Sink, phase, container, detail string) {
Emit(sink, ContainerStatusEvent{Phase: phase, Container: container, Detail: detail})
}
Expand Down
2 changes: 2 additions & 0 deletions internal/output/plain_format.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ func formatMessageEvent(e MessageEvent) string {
return "> Note: " + e.Text
case SeverityWarning:
return "> Warning: " + e.Text
case SeveritySecondary:
return e.Text
default:
return e.Text
}
Expand Down
6 changes: 5 additions & 1 deletion internal/ui/components/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ func RenderMessage(e output.MessageEvent) string {
func RenderWrappedMessage(e output.MessageEvent, width int) string {
prefixText, prefix := messagePrefix(e)
if prefixText == "" {
return styles.Message.Render(strings.Join(wrap.SoftWrap(e.Text, width), "\n"))
style := styles.Message
if e.Severity == output.SeveritySecondary {
style = styles.SecondaryMessage
}
return style.Render(strings.Join(wrap.SoftWrap(e.Text, width), "\n"))
}

if width <= len([]rune(prefixText))+1 {
Expand Down
31 changes: 31 additions & 0 deletions internal/ui/components/message_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package components

import (
"testing"

"github.com/localstack/lstk/internal/output"
"github.com/localstack/lstk/internal/ui/styles"
"github.com/stretchr/testify/assert"
)

func TestRenderMessage_SecondaryUsesSubduedStyle(t *testing.T) {
tests := []string{
"• Endpoint: localhost.localstack.cloud:4566",
"• Web app: https://app.localstack.cloud",
"> Tip: View emulator logs: lstk logs --follow",
}

for _, text := range tests {
assert.Equal(t, styles.SecondaryMessage.Render(text), RenderMessage(output.MessageEvent{
Severity: output.SeveritySecondary,
Text: text,
}))
}
}

func TestRenderMessage_LeavesRegularInfoLinesUnchanged(t *testing.T) {
assert.Equal(t, styles.Message.Render("hello"), RenderMessage(output.MessageEvent{
Severity: output.SeverityInfo,
Text: "hello",
}))
}
Loading