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
2 changes: 1 addition & 1 deletion apps/cli-go/internal/db/diff/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ func migrateBaseDatabase(ctx context.Context, config pgconn.Config, migrations [
}

func diffWithStream(ctx context.Context, env []string, script string, stdout io.Writer) error {
cmd := []string{"edge-runtime", "start", "--main-service=."}
cmd := utils.EdgeRuntimeStartCmd()
if viper.GetBool("DEBUG") {
cmd = append(cmd, "--verbose")
}
Expand Down
29 changes: 28 additions & 1 deletion apps/cli-go/internal/utils/edgeruntime.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package utils
import (
"bytes"
"context"
"fmt"
"net"
"strings"

"github.com/docker/docker/api/types/container"
Expand All @@ -11,10 +13,35 @@ import (
"github.com/spf13/viper"
)

// getFreeHostPort asks the OS for an unused TCP port on the host.
func getFreeHostPort() (int, error) {
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return 0, errors.Errorf("failed to allocate free port: %w", err)
}
defer listener.Close()
return listener.Addr().(*net.TCPAddr).Port, nil
}

// EdgeRuntimeStartCmd builds the base command for launching a one-shot Edge
// Runtime script. The runtime's HTTP listener is bound to a free host port so
// concurrent or leftover containers (which share the host network namespace
// because diff containers run with NetworkMode=host) don't collide on the
// edge-runtime default port, which surfaces as "Address already in use (os
// error 98)". See https://github.com/supabase/cli/issues/5407.
func EdgeRuntimeStartCmd() []string {
cmd := []string{"edge-runtime", "start", "--main-service=."}
// Skip the flag on the rare allocation failure to preserve prior behavior.
if port, err := getFreeHostPort(); err == nil {
cmd = append(cmd, fmt.Sprintf("--port=%d", port))
}
return cmd
}

// RunEdgeRuntimeScript executes a TypeScript program inside the configured Edge
// Runtime container and streams stdout/stderr back to the caller.
func RunEdgeRuntimeScript(ctx context.Context, env []string, script string, binds []string, errPrefix string, stdout, stderr *bytes.Buffer) error {
cmd := []string{"edge-runtime", "start", "--main-service=."}
cmd := EdgeRuntimeStartCmd()
if viper.GetBool("DEBUG") {
cmd = append(cmd, "--verbose")
}
Expand Down
47 changes: 47 additions & 0 deletions apps/cli-go/internal/utils/edgeruntime_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package utils

import (
"strconv"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestEdgeRuntimeStartCmd(t *testing.T) {
t.Run("binds an explicit free port", func(t *testing.T) {
cmd := EdgeRuntimeStartCmd()
// Base command must always be present.
assert.Equal(t, []string{"edge-runtime", "start", "--main-service=."}, cmd[:3])
// A --port flag avoids collisions on the edge-runtime default port (#5407).
var portFlag string
for _, arg := range cmd {
if strings.HasPrefix(arg, "--port=") {
portFlag = arg
}
}
require.NotEmpty(t, portFlag, "expected a --port flag to be set")
port, err := strconv.Atoi(strings.TrimPrefix(portFlag, "--port="))
require.NoError(t, err)
assert.Greater(t, port, 0)
assert.LessOrEqual(t, port, 65535)
})

t.Run("allocates a distinct port per invocation", func(t *testing.T) {
first := getPortArg(t, EdgeRuntimeStartCmd())
second := getPortArg(t, EdgeRuntimeStartCmd())
assert.NotEqual(t, first, second)
})
}

func getPortArg(t *testing.T, cmd []string) string {
t.Helper()
for _, arg := range cmd {
if strings.HasPrefix(arg, "--port=") {
return arg
}
}
require.FailNow(t, "missing --port flag")
return ""
}
Loading