|  | @@ -18,10 +18,14 @@ import (
 | 
	
		
			
				|  |  |  	"context"
 | 
	
		
			
				|  |  |  	"fmt"
 | 
	
		
			
				|  |  |  	"log"
 | 
	
		
			
				|  |  | +	"strings"
 | 
	
		
			
				|  |  | +	"time"
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	"github.com/compose-spec/compose-go/types"
 | 
	
		
			
				|  |  |  	"github.com/docker/compose/v2/pkg/api"
 | 
	
		
			
				|  |  | +	"github.com/docker/compose/v2/pkg/utils"
 | 
	
		
			
				|  |  |  	"github.com/fsnotify/fsnotify"
 | 
	
		
			
				|  |  | +	"github.com/jonboulle/clockwork"
 | 
	
		
			
				|  |  |  	"github.com/mitchellh/mapstructure"
 | 
	
		
			
				|  |  |  	"github.com/pkg/errors"
 | 
	
		
			
				|  |  |  	"golang.org/x/sync/errgroup"
 | 
	
	
		
			
				|  | @@ -30,10 +34,47 @@ import (
 | 
	
		
			
				|  |  |  type DevelopmentConfig struct {
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +const quietPeriod = 2 * time.Second
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  func (s *composeService) Watch(ctx context.Context, project *types.Project, services []string, options api.WatchOptions) error {
 | 
	
		
			
				|  |  |  	fmt.Fprintln(s.stderr(), "not implemented yet")
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	eg, ctx := errgroup.WithContext(ctx)
 | 
	
		
			
				|  |  | +	needRefresh := make(chan string)
 | 
	
		
			
				|  |  | +	eg.Go(func() error {
 | 
	
		
			
				|  |  | +		clock := clockwork.NewRealClock()
 | 
	
		
			
				|  |  | +		debounce(ctx, clock, quietPeriod, needRefresh, func(services []string) {
 | 
	
		
			
				|  |  | +			fmt.Fprintf(s.stderr(), "Updating %s after changes were detected\n", strings.Join(services, ", "))
 | 
	
		
			
				|  |  | +			imageIds, err := s.build(ctx, project, api.BuildOptions{
 | 
	
		
			
				|  |  | +				Services: services,
 | 
	
		
			
				|  |  | +			})
 | 
	
		
			
				|  |  | +			if err != nil {
 | 
	
		
			
				|  |  | +				fmt.Fprintf(s.stderr(), "Build failed")
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +			for i, service := range project.Services {
 | 
	
		
			
				|  |  | +				if id, ok := imageIds[service.Name]; ok {
 | 
	
		
			
				|  |  | +					service.Image = id
 | 
	
		
			
				|  |  | +				}
 | 
	
		
			
				|  |  | +				project.Services[i] = service
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +			err = s.Up(ctx, project, api.UpOptions{
 | 
	
		
			
				|  |  | +				Create: api.CreateOptions{
 | 
	
		
			
				|  |  | +					Services: services,
 | 
	
		
			
				|  |  | +					Inherit:  true,
 | 
	
		
			
				|  |  | +				},
 | 
	
		
			
				|  |  | +				Start: api.StartOptions{
 | 
	
		
			
				|  |  | +					Services: services,
 | 
	
		
			
				|  |  | +					Project:  project,
 | 
	
		
			
				|  |  | +				},
 | 
	
		
			
				|  |  | +			})
 | 
	
		
			
				|  |  | +			if err != nil {
 | 
	
		
			
				|  |  | +				fmt.Fprintf(s.stderr(), "Application failed to start after update")
 | 
	
		
			
				|  |  | +			}
 | 
	
		
			
				|  |  | +		})
 | 
	
		
			
				|  |  | +		return nil
 | 
	
		
			
				|  |  | +	})
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  	err := project.WithServices(services, func(service types.ServiceConfig) error {
 | 
	
		
			
				|  |  |  		var config DevelopmentConfig
 | 
	
		
			
				|  |  |  		if y, ok := service.Extensions["x-develop"]; ok {
 | 
	
	
		
			
				|  | @@ -64,6 +105,7 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv
 | 
	
		
			
				|  |  |  					return nil
 | 
	
		
			
				|  |  |  				case event := <-watcher.Events:
 | 
	
		
			
				|  |  |  					log.Println("fs event :", event.String())
 | 
	
		
			
				|  |  | +					needRefresh <- service.Name
 | 
	
		
			
				|  |  |  				case err := <-watcher.Errors:
 | 
	
		
			
				|  |  |  					return err
 | 
	
		
			
				|  |  |  				}
 | 
	
	
		
			
				|  | @@ -77,3 +119,23 @@ func (s *composeService) Watch(ctx context.Context, project *types.Project, serv
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	return eg.Wait()
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +func debounce(ctx context.Context, clock clockwork.Clock, delay time.Duration, input chan string, fn func(services []string)) {
 | 
	
		
			
				|  |  | +	services := utils.Set[string]{}
 | 
	
		
			
				|  |  | +	t := clock.AfterFunc(delay, func() {
 | 
	
		
			
				|  |  | +		if len(services) > 0 {
 | 
	
		
			
				|  |  | +			refresh := services.Elements()
 | 
	
		
			
				|  |  | +			services.Clear()
 | 
	
		
			
				|  |  | +			fn(refresh)
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +	})
 | 
	
		
			
				|  |  | +	for {
 | 
	
		
			
				|  |  | +		select {
 | 
	
		
			
				|  |  | +		case <-ctx.Done():
 | 
	
		
			
				|  |  | +			return
 | 
	
		
			
				|  |  | +		case service := <-input:
 | 
	
		
			
				|  |  | +			t.Reset(delay)
 | 
	
		
			
				|  |  | +			services.Add(service)
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +}
 |