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
5 changes: 5 additions & 0 deletions frontend/app/store/wshclientapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ class RpcApiType {
return client.wshRpcCall("captureblockscreenshot", data, opts);
}

// command "checkgoversion" [call]
CheckGoVersionCommand(client: WshClient, opts?: RpcOpts): Promise<CommandCheckGoVersionRtnData> {
return client.wshRpcCall("checkgoversion", null, opts);
}

// command "connconnect" [call]
ConnConnectCommand(client: WshClient, data: ConnRequest, opts?: RpcOpts): Promise<void> {
return client.wshRpcCall("connconnect", data, opts);
Expand Down
8 changes: 8 additions & 0 deletions frontend/types/gotypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,14 @@ declare global {
blockid: string;
};

// wshrpc.CommandCheckGoVersionRtnData
type CommandCheckGoVersionRtnData = {
gostatus: string;
gopath: string;
goversion: string;
errorstring?: string;
};

// wshrpc.CommandControllerAppendOutputData
type CommandControllerAppendOutputData = {
blockid: string;
Expand Down
6 changes: 6 additions & 0 deletions pkg/wshrpc/wshclient/wshclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ func CaptureBlockScreenshotCommand(w *wshutil.WshRpc, data wshrpc.CommandCapture
return resp, err
}

// command "checkgoversion", wshserver.CheckGoVersionCommand
func CheckGoVersionCommand(w *wshutil.WshRpc, opts *wshrpc.RpcOpts) (*wshrpc.CommandCheckGoVersionRtnData, error) {
resp, err := sendRpcRequestCallHelper[*wshrpc.CommandCheckGoVersionRtnData](w, "checkgoversion", nil, opts)
return resp, err
}

// command "connconnect", wshserver.ConnConnectCommand
func ConnConnectCommand(w *wshutil.WshRpc, data wshrpc.ConnRequest, opts *wshrpc.RpcOpts) error {
_, err := sendRpcRequestCallHelper[any](w, "connconnect", data, opts)
Expand Down
9 changes: 9 additions & 0 deletions pkg/wshrpc/wshrpctypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ const (
Command_StartBuilder = "startbuilder"
Command_GetBuilderStatus = "getbuilderstatus"
Command_GetBuilderOutput = "getbuilderoutput"
Command_CheckGoVersion = "checkgoversion"

// electron
Command_ElectronEncrypt = "electronencrypt"
Expand Down Expand Up @@ -335,6 +336,7 @@ type WshRpcInterface interface {
StartBuilderCommand(ctx context.Context, data CommandStartBuilderData) error
GetBuilderStatusCommand(ctx context.Context, builderId string) (*BuilderStatusData, error)
GetBuilderOutputCommand(ctx context.Context, builderId string) ([]string, error)
CheckGoVersionCommand(ctx context.Context) (*CommandCheckGoVersionRtnData, error)

// proc
VDomRenderCommand(ctx context.Context, data vdom.VDomFrontendUpdate) chan RespOrErrorUnion[*vdom.VDomBackendUpdate]
Expand Down Expand Up @@ -1018,6 +1020,13 @@ type BuilderStatusData struct {
Version int `json:"version"`
}

type CommandCheckGoVersionRtnData struct {
GoStatus string `json:"gostatus"`
GoPath string `json:"gopath"`
GoVersion string `json:"goversion"`
ErrorString string `json:"errorstring,omitempty"`
}

type CommandElectronEncryptData struct {
PlainText string `json:"plaintext"`
}
Expand Down
16 changes: 16 additions & 0 deletions pkg/wshrpc/wshserver/wshserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import (
"github.com/wavetermdev/waveterm/pkg/wsl"
"github.com/wavetermdev/waveterm/pkg/wslconn"
"github.com/wavetermdev/waveterm/pkg/wstore"
"github.com/wavetermdev/waveterm/tsunami/build"
)

var InvalidWslDistroNames = []string{"docker-desktop", "docker-desktop-data"}
Expand Down Expand Up @@ -1073,6 +1074,21 @@ func (ws *WshServer) GetBuilderOutputCommand(ctx context.Context, builderId stri
return bc.GetOutput(), nil
}

func (ws *WshServer) CheckGoVersionCommand(ctx context.Context) (*wshrpc.CommandCheckGoVersionRtnData, error) {
watcher := wconfig.GetWatcher()
fullConfig := watcher.GetFullConfig()
goPath := fullConfig.Settings.TsunamiGoPath

result := build.CheckGoVersion(goPath)

return &wshrpc.CommandCheckGoVersionRtnData{
GoStatus: result.GoStatus,
GoPath: result.GoPath,
GoVersion: result.GoVersion,
ErrorString: result.ErrorString,
}, nil
}

func (ws *WshServer) RecordTEventCommand(ctx context.Context, data telemetrydata.TEvent) error {
err := telemetry.RecordTEvent(ctx, &data)
if err != nil {
Expand Down
138 changes: 103 additions & 35 deletions tsunami/build/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ func (opts BuildOpts) getNodePath() string {
return "node"
}

type GoVersionCheckResult struct {
GoStatus string
GoPath string
GoVersion string
ErrorString string
}

func FindGoExecutable() (string, error) {
// First try the standard PATH lookup
if goPath, err := exec.LookPath("go"); err == nil {
Expand Down Expand Up @@ -156,72 +163,133 @@ func FindGoExecutable() (string, error) {
return "", fmt.Errorf("go command not found in PATH or common installation locations")
}

func verifyEnvironment(verbose bool, opts BuildOpts) (*BuildEnv, error) {
oc := opts.OutputCapture

if opts.SdkVersion == "" && opts.SdkReplacePath == "" {
return nil, fmt.Errorf("either SdkVersion or SdkReplacePath must be set")
}

if opts.SdkVersion != "" {
versionRegex := regexp.MustCompile(`^v\d+\.\d+\.\d+`)
if !versionRegex.MatchString(opts.SdkVersion) {
return nil, fmt.Errorf("SdkVersion must be in semantic version format (e.g., v0.0.0), got: %s", opts.SdkVersion)
}
}

func CheckGoVersion(customGoPath string) GoVersionCheckResult {
var goPath string
var err error

if opts.GoPath != "" {
goPath = opts.GoPath
if verbose {
oc.Printf("Using custom go path: %s", opts.GoPath)
}
if customGoPath != "" {
goPath = customGoPath
} else {
goPath, err = FindGoExecutable()
if err != nil {
return nil, fmt.Errorf("go command not found: %w", err)
}
if verbose {
oc.Printf("Using go path: %s", goPath)
return GoVersionCheckResult{
GoStatus: "notfound",
GoPath: "",
GoVersion: "",
ErrorString: "",
}
}
}

// Run go version command
cmd := exec.Command(goPath, "version")
output, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("failed to run 'go version': %w", err)
return GoVersionCheckResult{
GoStatus: "error",
GoPath: goPath,
GoVersion: "",
ErrorString: fmt.Sprintf("failed to run 'go version': %v", err),
}
}

// Parse go version output and check for 1.22+
versionStr := strings.TrimSpace(string(output))
if verbose {
oc.Printf("Found %s", versionStr)
}

// Extract version like "go1.22.0" from output
versionRegex := regexp.MustCompile(`go(1\.\d+)`)
matches := versionRegex.FindStringSubmatch(versionStr)
if len(matches) < 2 {
return nil, fmt.Errorf("unable to parse go version from: %s", versionStr)
return GoVersionCheckResult{
GoStatus: "error",
GoPath: goPath,
GoVersion: versionStr,
ErrorString: fmt.Sprintf("unable to parse go version from: %s", versionStr),
}
}

goVersion := matches[1]

// Check if version is 1.22+
minorRegex := regexp.MustCompile(`1\.(\d+)`)
minorMatches := minorRegex.FindStringSubmatch(goVersion)
if len(minorMatches) < 2 {
return nil, fmt.Errorf("unable to parse minor version from: %s", goVersion)
return GoVersionCheckResult{
GoStatus: "error",
GoPath: goPath,
GoVersion: versionStr,
ErrorString: fmt.Sprintf("unable to parse minor version from: %s", goVersion),
}
}

minor, err := strconv.Atoi(minorMatches[1])
if err != nil || minor < MinSupportedGoMinorVersion {
return nil, fmt.Errorf("go version 1.%d or higher required, found: %s", MinSupportedGoMinorVersion, versionStr)
if err != nil {
return GoVersionCheckResult{
GoStatus: "error",
GoPath: goPath,
GoVersion: versionStr,
ErrorString: fmt.Sprintf("failed to parse minor version: %v", err),
}
}

if minor < MinSupportedGoMinorVersion {
return GoVersionCheckResult{
GoStatus: "badversion",
GoPath: goPath,
GoVersion: versionStr,
ErrorString: "",
}
}

return GoVersionCheckResult{
GoStatus: "ok",
GoPath: goPath,
GoVersion: versionStr,
ErrorString: "",
}
}

func verifyEnvironment(verbose bool, opts BuildOpts) (*BuildEnv, error) {
oc := opts.OutputCapture

if opts.SdkVersion == "" && opts.SdkReplacePath == "" {
return nil, fmt.Errorf("either SdkVersion or SdkReplacePath must be set")
}

if opts.SdkVersion != "" {
versionRegex := regexp.MustCompile(`^v\d+\.\d+\.\d+`)
if !versionRegex.MatchString(opts.SdkVersion) {
return nil, fmt.Errorf("SdkVersion must be in semantic version format (e.g., v0.0.0), got: %s", opts.SdkVersion)
}
}

result := CheckGoVersion(opts.GoPath)

switch result.GoStatus {
case "notfound":
return nil, fmt.Errorf("go command not found")
case "badversion":
return nil, fmt.Errorf("go version 1.%d or higher required, found: %s", MinSupportedGoMinorVersion, result.GoVersion)
case "error":
return nil, fmt.Errorf("%s", result.ErrorString)
case "ok":
if verbose {
if opts.GoPath != "" {
oc.Printf("Using custom go path: %s", result.GoPath)
} else {
oc.Printf("Using go path: %s", result.GoPath)
}
oc.Printf("Found %s", result.GoVersion)
}
default:
return nil, fmt.Errorf("unexpected go status: %s", result.GoStatus)
}

versionRegex := regexp.MustCompile(`go(1\.\d+)`)
matches := versionRegex.FindStringSubmatch(result.GoVersion)
if len(matches) < 2 {
return nil, fmt.Errorf("unable to parse go version from: %s", result.GoVersion)
}
goVersion := matches[1]

var err error

// Check if node is available
if opts.NodePath != "" {
// Custom node path specified - verify it's absolute and executable
Expand Down
Loading