fix: provider output handling and watch rebuild re-invocation

Provider info and error messages containing newlines broke the TTY
progress display (timer drifting to a new line, broken cursor
movement). Extract only the first line for progress events via
firstLine(). Full messages remain available through the provider's
own debug message type.

Skip provider services during watch rebuild convergence by adding a
SkipProviders flag to CreateOptions, set only by the watch rebuild
path. This prevents unnecessary re-invocation of providers on every
file change while preserving normal provider execution for all other
commands (up, create, run, scale).

Signed-off-by: Guillaume Lours <glours@users.noreply.github.com>
This commit is contained in:
Guillaume Lours 2026-04-14 11:26:21 +02:00 committed by Guillaume Lours
parent d518da2419
commit 9eb8966705
4 changed files with 23 additions and 5 deletions

View file

@ -278,6 +278,8 @@ type CreateOptions struct {
Timeout *time.Duration
// QuietPull makes the pulling process quiet
QuietPull bool
// SkipProviders skips provider services during convergence (e.g. watch rebuild)
SkipProviders bool
}
// StartOptions group options of the Start API

View file

@ -100,6 +100,12 @@ func (c *convergence) apply(ctx context.Context, project *types.Project, options
return err
}
// Skip provider services when the caller opted out (e.g. watch rebuild),
// since providers were already set up during initial "up".
if service.Provider != nil && options.SkipProviders {
return nil
}
return tracing.SpanWrapFunc("service/apply", tracing.ServiceOptions(service), func(ctx context.Context) error {
strategy := options.RecreateDependencies
if slices.Contains(options.Services, name) {

View file

@ -125,10 +125,10 @@ func (s *composeService) executePlugin(cmd *exec.Cmd, command string, service ty
}
switch msg.Type {
case ErrorType:
s.events.On(newEvent(service.Name, api.Error, msg.Message))
s.events.On(newEvent(service.Name, api.Error, firstLine(msg.Message)))
return nil, errors.New(msg.Message)
case InfoType:
s.events.On(newEvent(service.Name, api.Working, msg.Message))
s.events.On(newEvent(service.Name, api.Working, firstLine(msg.Message)))
case SetEnvType:
key, val, found := strings.Cut(msg.Message, "=")
if !found {
@ -281,3 +281,12 @@ func (c CommandMetadata) CheckRequiredParameters(provider types.ServiceProviderC
}
return nil
}
// firstLine returns the first line of s, stripping any trailing newlines.
func firstLine(s string) string {
s = strings.TrimRight(s, "\n")
if i := strings.IndexByte(s, '\n'); i >= 0 {
return s[:i]
}
return s
}

View file

@ -662,9 +662,10 @@ func (s *composeService) rebuild(ctx context.Context, project *types.Project, se
options.LogTo.Log(api.WatchLogger, fmt.Sprintf("service(s) %q successfully built", services))
err = s.create(ctx, project, api.CreateOptions{
Services: services,
Inherit: true,
Recreate: api.RecreateForce,
Services: services,
Inherit: true,
Recreate: api.RecreateForce,
SkipProviders: true,
})
if err != nil {
options.LogTo.Log(api.WatchLogger, fmt.Sprintf("Failed to recreate services after update. Error: %v", err))