Skip to content
Open
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
128 changes: 70 additions & 58 deletions cmd/sst/mosaic.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,11 +220,6 @@ func CmdMosaic(c *cli.Cli) error {
return server.Start(c.Context, p)
})

wg.Go(func() error {
defer c.Cancel()
return deployer.Start(c.Context, p, server)
})

currentExecutable, _ := os.Executable()

mode := c.String("mode")
Expand All @@ -236,6 +231,14 @@ func CmdMosaic(c *cli.Cli) error {
}
}

// Subscribe to CompleteEvent BEFORE starting deployer to not miss any events
completeEvts := bus.Subscribe(&project.CompleteEvent{})

wg.Go(func() error {
defer c.Cancel()
return deployer.Start(c.Context, p, server)
})

if mode == "multi" {
multi, err := multiplexer.New()
if err != nil {
Expand All @@ -251,49 +254,52 @@ func CmdMosaic(c *cli.Cli) error {
defer func() {
multi.Exit()
}()

processCompleteEventForMultiplexer := func(evt *project.CompleteEvent) {
for _, d := range evt.Devs {
if d.Command == "" {
continue
}
dir := filepath.Join(cwd, d.Directory)
title := d.Title
if title == "" {
title = d.Name
}
multi.AddProcess(d.Name, append([]string{currentExecutable, "dev"}), "→", title, dir, true, d.Autostart, append([]string{"SST_CHILD=" + d.Name}, multiEnv...)...)
}

for name := range evt.Tunnels {
multi.AddProcess("tunnel", []string{currentExecutable, "tunnel", "--stage", p.App().Stage}, "⇌", "Tunnel", "", true, true, append(
multiEnv,
"SST_LOG="+p.PathLog("tunnel_"+name),
)...)
}

if len(evt.Tasks) > 0 {
multi.AddProcess("task", []string{currentExecutable, "ui", "--filter=task"}, "⧉", "Tasks", "", false, true, append(multiEnv, "SST_LOG="+p.PathLog("ui-task"))...)
}
}

go func() {
multi.Start()
}()
wg.Go(func() error {
evts := bus.Subscribe(&project.CompleteEvent{})
defer c.Cancel()

// Wait for multiplexer to be ready before posting events
<-multi.Ready()

for {
select {
case <-c.Context.Done():
return nil
case unknown := <-evts:
case unknown := <-completeEvts:
switch evt := unknown.(type) {
case *project.CompleteEvent:
for _, d := range evt.Devs {
if d.Command == "" {
continue
}
dir := filepath.Join(cwd, d.Directory)
title := d.Title
if title == "" {
title = d.Name
}
multi.AddProcess(
d.Name,
append([]string{currentExecutable, "dev"}),
"→",
title,
dir,
true,
d.Autostart,
append([]string{"SST_CHILD=" + d.Name}, multiEnv...)...,
)
}
for name := range evt.Tunnels {
multi.AddProcess("tunnel", []string{currentExecutable, "tunnel", "--stage", p.App().Stage}, "⇌", "Tunnel", "", true, true, append(
multiEnv,
"SST_LOG="+p.PathLog("tunnel_"+name),
)...)
}
if len(evt.Tasks) > 0 {
multi.AddProcess("task", []string{currentExecutable, "ui", "--filter=task"}, "⧉", "Tasks", "", false, true, append(multiEnv, "SST_LOG="+p.PathLog("ui-task"))...)
if evt.Old {
continue
}
break
processCompleteEventForMultiplexer(evt)
}
}
}
Expand All @@ -311,42 +317,48 @@ func CmdMosaic(c *cli.Cli) error {
mono.AddProcess("deploy", []string{currentExecutable, "ui", "--filter=sst"}, "", "SST")
mono.AddProcess("function", []string{currentExecutable, "ui", "--filter=function"}, "", "Function")

processCompleteEventForMonoplexer := func(evt *project.CompleteEvent) {
for _, d := range evt.Devs {
if d.Command == "" {
continue
}
dir := filepath.Join(cwd, d.Directory)
words, _ := shellquote.Split(d.Command)
title := d.Title
if title == "" {
title = d.Name
}
mono.AddProcess(
d.Name,
append([]string{currentExecutable, "dev", "--"}, words...),
dir,
title,
)
}
for range evt.Tunnels {
mono.AddProcess("tunnel", []string{currentExecutable, "tunnel", "--stage", p.App().Stage}, "", "Tunnel")
}
}

wg.Go(func() error {
defer c.Cancel()
return mono.Start(c.Context)
})

wg.Go(func() error {
evts := bus.Subscribe(&project.CompleteEvent{})
defer c.Cancel()

for {
select {
case <-c.Context.Done():
return nil
case unknown := <-evts:
case unknown := <-completeEvts:
switch evt := unknown.(type) {
case *project.CompleteEvent:
for _, d := range evt.Devs {
if d.Command == "" {
continue
}
dir := filepath.Join(cwd, d.Directory)
words, _ := shellquote.Split(d.Command)
title := d.Title
if title == "" {
title = d.Name
}
mono.AddProcess(
d.Name,
append([]string{currentExecutable, "dev", "--"}, words...),
dir,
title,
)
}
for range evt.Tunnels {
mono.AddProcess("tunnel", []string{currentExecutable, "tunnel", "--stage", p.App().Stage}, "", "Tunnel")
if evt.Old {
continue
}
break
processCompleteEventForMonoplexer(evt)
}
}
}
Expand Down
6 changes: 5 additions & 1 deletion cmd/sst/mosaic/dev/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@ func Start(ctx context.Context, p *project.Project, server *server.Server) error
case <-ctx.Done():
return nil
case evt := <-evts:
complete = evt.(*project.CompleteEvent)
casted := evt.(*project.CompleteEvent)
if casted.Old {
continue
}
complete = casted
}
}
})
Expand Down
94 changes: 59 additions & 35 deletions cmd/sst/mosaic/multiplexer/multiplexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,16 @@ type Multiplexer struct {

dragging bool
click *tcell.EventMouse
ready chan struct{}
procChan chan *EventProcess
}

func New() (*Multiplexer, error) {
var err error
result := &Multiplexer{}
result.processes = []*pane{}
result.ready = make(chan struct{})
result.procChan = make(chan *EventProcess, 1000)
result.screen, err = tcell.NewScreen()
if err != nil {
return nil, err
Expand All @@ -59,6 +63,11 @@ func New() (*Multiplexer, error) {
return result, nil
}

// Ready returns a channel that is closed when the multiplexer is ready to receive events
func (s *Multiplexer) Ready() <-chan struct{} {
return s.ready
}

func (s *Multiplexer) mainRect() (int, int) {
return s.width - SIDEBAR_WIDTH + 1, s.height
}
Expand All @@ -80,6 +89,15 @@ func (s *Multiplexer) Start() {
}()

s.resize(s.screen.Size())
close(s.ready)

// Process events from procChan by forwarding to screen events
go func() {
for evt := range s.procChan {
// Use PostEventWait to ensure events are not dropped
s.screen.PostEventWait(evt)
}
}()

for {
unknown := s.screen.PollEvent()
Expand All @@ -103,41 +121,7 @@ func (s *Multiplexer) Start() {
return

case *EventProcess:
for _, p := range s.processes {
if p.key == evt.Key {
if p.dead && evt.Autostart {
p.start()
s.sort()
s.draw()
}
return
}
}
proc := &pane{
icon: evt.Icon,
key: evt.Key,
dir: evt.Cwd,
title: evt.Title,
args: evt.Args,
killable: evt.Killable,
env: evt.Env,
}
term := tcellterm.New()
term.SetSurface(s.main)
term.Attach(func(ev tcell.Event) {
s.screen.PostEvent(ev)
})
proc.vt = term
if evt.Autostart {
proc.start()
}
if !evt.Autostart {
proc.vt.Start(process.Command("echo", evt.Key+" has auto-start disabled, press enter to start."))
proc.dead = true
}
s.processes = append(s.processes, proc)
s.sort()
s.draw()
s.InitProcess(evt.Key, evt.Args, evt.Icon, evt.Title, evt.Cwd, evt.Killable, evt.Autostart, evt.Env...)
break

case *tcell.EventMouse:
Expand Down Expand Up @@ -381,3 +365,43 @@ func (s *Multiplexer) copy() {
encoded := base64.StdEncoding.EncodeToString([]byte(data))
fmt.Fprintf(os.Stdout, "\x1b]52;c;%s\x07", encoded)
}

func (s *Multiplexer) InitProcess(key string, args []string, icon string, title string, cwd string, killable bool, autostart bool, env ...string) {
for _, p := range s.processes {
if p.key == key {
if p.dead && autostart {
p.start()
s.sort()
s.draw()
}
return
}
}

proc := &pane{
icon: icon,
key: key,
dir: cwd,
title: title,
args: args,
killable: killable,
env: env,
}
term := tcellterm.New()
term.SetSurface(s.main)
term.Attach(func(ev tcell.Event) {
s.screen.PostEvent(ev)
})
proc.vt = term

if autostart {
proc.start()
} else {
proc.vt.Start(process.Command("echo", key+" has auto-start disabled, press enter to start."))
proc.dead = true
}

s.processes = append(s.processes, proc)
s.sort()
s.draw()
}
4 changes: 2 additions & 2 deletions cmd/sst/mosaic/multiplexer/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ type EventProcess struct {
}

func (s *Multiplexer) AddProcess(key string, args []string, icon string, title string, cwd string, killable bool, autostart bool, env ...string) {
s.screen.PostEvent(&EventProcess{
s.procChan <- &EventProcess{
Key: key,
Args: args,
Icon: icon,
Expand All @@ -48,7 +48,7 @@ func (s *Multiplexer) AddProcess(key string, args []string, icon string, title s
Killable: killable,
Autostart: autostart,
Env: env,
})
}
}

func (p *pane) start() error {
Expand Down