diff --git a/src/pkg/cli/compose.go b/src/pkg/cli/compose.go index 609bae76e..caa499e77 100644 --- a/src/pkg/cli/compose.go +++ b/src/pkg/cli/compose.go @@ -70,7 +70,8 @@ func NormalizeServiceName(s string) string { } func warnf(format string, args ...interface{}) { - logrus.Warnf(format, args...) + // By default, the logrus StandardLogger writes to os.Stderr + logrus.Warnf(term.MarkDown(term.Stderr, format), args...) term.HadWarnings = true } diff --git a/src/pkg/cli/compose_validation.go b/src/pkg/cli/compose_validation.go index 0e7a8d1ee..43d0f6f22 100644 --- a/src/pkg/cli/compose_validation.go +++ b/src/pkg/cli/compose_validation.go @@ -29,7 +29,7 @@ func validateProject(project *compose.Project) error { warnf("unsupported compose directive: read_only") } if svccfg.Restart == "" { - warnf("missing compose directive: restart; assuming 'unless-stopped' (add 'restart' to silence)") + warnf("missing compose directive: `restart`; assuming 'unless-stopped' (add 'restart' to silence)") } else if svccfg.Restart != "always" && svccfg.Restart != "unless-stopped" { warnf("unsupported compose directive: restart; assuming 'unless-stopped' (add 'restart' to silence)") } diff --git a/src/pkg/quota/quota.go b/src/pkg/quota/quota.go index 9b8bd1186..671f1c260 100644 --- a/src/pkg/quota/quota.go +++ b/src/pkg/quota/quota.go @@ -20,19 +20,19 @@ type Quotas struct { func (q Quotas) Validate(service *defangv1.Service) error { if service.Name == "" { - return errors.New("service name is required") // CodeInvalidArgument + return errors.New("service `name:` is required") // CodeInvalidArgument } if service.Build != nil { if service.Build.Context == "" { - return errors.New("build.context is required") // CodeInvalidArgument + return errors.New("build `context:` is required") // CodeInvalidArgument } if service.Build.ShmSize > q.ShmSizeMiB || service.Build.ShmSize < 0 { - return fmt.Errorf("build.shm_size exceeds quota (max %v MiB)", q.ShmSizeMiB) // CodeInvalidArgument + return fmt.Errorf("build `shm_size:` exceeds quota (max %v MiB)", q.ShmSizeMiB) // CodeInvalidArgument } } else { if service.Image == "" { - return errors.New("missing image or build") // CodeInvalidArgument + return errors.New("each service must have either `image:` or `build:`") // CodeInvalidArgument } } @@ -91,7 +91,7 @@ func (q Quotas) Validate(service *defangv1.Service) error { return fmt.Errorf("ingress port requires a CMD healthcheck") } default: - return fmt.Errorf("unsupported healthcheck: %v", service.Healthcheck.Test) + return fmt.Errorf("unsupported `healthcheck:` %v", service.Healthcheck.Test) } } diff --git a/src/pkg/quota/quota_test.go b/src/pkg/quota/quota_test.go index 1a094ca2a..238bb025f 100644 --- a/src/pkg/quota/quota_test.go +++ b/src/pkg/quota/quota_test.go @@ -15,22 +15,22 @@ func TestValidate(t *testing.T) { { name: "empty service", service: &defangv1.Service{}, - wantErr: "service name is required", + wantErr: "service `name:` is required", }, { name: "no image, no build", service: &defangv1.Service{Name: "test"}, - wantErr: "missing image or build", + wantErr: "each service must have either `image:` or `build:`", }, { name: "empty build", service: &defangv1.Service{Name: "test", Build: &defangv1.Build{}}, - wantErr: "build.context is required", + wantErr: "build `context:` is required", }, { name: "shm size exceeds quota", service: &defangv1.Service{Name: "test", Build: &defangv1.Build{Context: ".", ShmSize: 30721}}, - wantErr: "build.shm_size exceeds quota (max 30720 MiB)", + wantErr: "build `shm_size:` exceeds quota (max 30720 MiB)", }, { name: "port 0 out of range", @@ -145,7 +145,7 @@ func TestValidate(t *testing.T) { Test: []string{"BLAH"}, }, }, - wantErr: "unsupported healthcheck: [BLAH]", + wantErr: "unsupported `healthcheck:` [BLAH]", }, { name: "too many replicas", diff --git a/src/pkg/term/colorizer.go b/src/pkg/term/colorizer.go index f18d45ec8..029e7eaee 100644 --- a/src/pkg/term/colorizer.go +++ b/src/pkg/term/colorizer.go @@ -3,6 +3,7 @@ package term import ( "fmt" "os" + "regexp" "github.com/muesli/termenv" "golang.org/x/term" @@ -43,6 +44,19 @@ func ForceColor(color bool) { } } +var backticksRegex = regexp.MustCompile("`([^`]+)`") + +func markdown(msg string) string { + return backticksRegex.ReplaceAllString(msg, termenv.CSI+"7m$1"+termenv.CSI+"27m") +} + +func MarkDown(w *termenv.Output, msg string) string { + if !DoColor(w) { + return msg + } + return markdown(msg) +} + func output(w *termenv.Output, c Color, msg string) (int, error) { if len(msg) == 0 { return 0, nil @@ -50,6 +64,7 @@ func output(w *termenv.Output, c Color, msg string) (int, error) { if DoColor(w) { w.WriteString(termenv.CSI + c.Sequence(false) + "m") defer w.Reset() + msg = markdown(msg) } return w.WriteString(msg) }