@@ -9,26 +9,33 @@ import (
99 "regexp"
1010 "strconv"
1111 "strings"
12+
13+ "golang.org/x/mod/modfile"
1214)
1315
1416type BuildOpts struct {
15- Dir string
16- Verbose bool
17- DistPath string
17+ Dir string
18+ Verbose bool
19+ DistPath string
20+ SdkReplacePath string
21+ }
22+
23+ type BuildEnv struct {
24+ GoVersion string
1825}
1926
20- func verifyEnvironment (verbose bool ) error {
27+ func verifyEnvironment (verbose bool ) ( * BuildEnv , error ) {
2128 // Check if go is in PATH
2229 goPath , err := exec .LookPath ("go" )
2330 if err != nil {
24- return fmt .Errorf ("go command not found in PATH: %w" , err )
31+ return nil , fmt .Errorf ("go command not found in PATH: %w" , err )
2532 }
2633
2734 // Run go version command
2835 cmd := exec .Command (goPath , "version" )
2936 output , err := cmd .Output ()
3037 if err != nil {
31- return fmt .Errorf ("failed to run 'go version': %w" , err )
38+ return nil , fmt .Errorf ("failed to run 'go version': %w" , err )
3239 }
3340
3441 // Parse go version output and check for 1.21+
@@ -38,21 +45,30 @@ func verifyEnvironment(verbose bool) error {
3845 }
3946
4047 // Extract version like "go1.21.0" from output
41- versionRegex := regexp .MustCompile (`go1\.( \d+)` )
48+ versionRegex := regexp .MustCompile (`go(1\. \d+)` )
4249 matches := versionRegex .FindStringSubmatch (versionStr )
4350 if len (matches ) < 2 {
44- return fmt .Errorf ("unable to parse go version from: %s" , versionStr )
51+ return nil , fmt .Errorf ("unable to parse go version from: %s" , versionStr )
52+ }
53+
54+ goVersion := matches [1 ]
55+
56+ // Check if version is 1.21+
57+ minorRegex := regexp .MustCompile (`1\.(\d+)` )
58+ minorMatches := minorRegex .FindStringSubmatch (goVersion )
59+ if len (minorMatches ) < 2 {
60+ return nil , fmt .Errorf ("unable to parse minor version from: %s" , goVersion )
4561 }
4662
47- minor , err := strconv .Atoi (matches [1 ])
63+ minor , err := strconv .Atoi (minorMatches [1 ])
4864 if err != nil || minor < 21 {
49- return fmt .Errorf ("go version 1.21 or higher required, found: %s" , versionStr )
65+ return nil , fmt .Errorf ("go version 1.21 or higher required, found: %s" , versionStr )
5066 }
5167
5268 // Check if npx is in PATH
5369 _ , err = exec .LookPath ("npx" )
5470 if err != nil {
55- return fmt .Errorf ("npx command not found in PATH: %w" , err )
71+ return nil , fmt .Errorf ("npx command not found in PATH: %w" , err )
5672 }
5773
5874 if verbose {
@@ -63,13 +79,13 @@ func verifyEnvironment(verbose bool) error {
6379 tailwindCmd := exec .Command ("npx" , "@tailwindcss/cli" )
6480 tailwindOutput , err := tailwindCmd .CombinedOutput ()
6581 if err != nil {
66- return fmt .Errorf ("failed to run 'npx @tailwindcss/cli': %w" , err )
82+ return nil , fmt .Errorf ("failed to run 'npx @tailwindcss/cli': %w" , err )
6783 }
6884
6985 tailwindStr := strings .TrimSpace (string (tailwindOutput ))
7086 lines := strings .Split (tailwindStr , "\n " )
7187 if len (lines ) == 0 {
72- return fmt .Errorf ("no output from tailwindcss command" )
88+ return nil , fmt .Errorf ("no output from tailwindcss command" )
7389 }
7490
7591 firstLine := lines [0 ]
@@ -79,14 +95,76 @@ func verifyEnvironment(verbose bool) error {
7995
8096 // Check for v4 (format: "≈ tailwindcss v4.1.12")
8197 tailwindRegex := regexp .MustCompile (`tailwindcss v(\d+)` )
82- matches = tailwindRegex .FindStringSubmatch (firstLine )
83- if len (matches ) < 2 {
84- return fmt .Errorf ("unable to parse tailwindcss version from: %s" , firstLine )
98+ tailwindMatches : = tailwindRegex .FindStringSubmatch (firstLine )
99+ if len (tailwindMatches ) < 2 {
100+ return nil , fmt .Errorf ("unable to parse tailwindcss version from: %s" , firstLine )
85101 }
86102
87- majorVersion , err := strconv .Atoi (matches [1 ])
103+ majorVersion , err := strconv .Atoi (tailwindMatches [1 ])
88104 if err != nil || majorVersion != 4 {
89- return fmt .Errorf ("tailwindcss v4 required, found: %s" , firstLine )
105+ return nil , fmt .Errorf ("tailwindcss v4 required, found: %s" , firstLine )
106+ }
107+
108+ return & BuildEnv {GoVersion : goVersion }, nil
109+ }
110+
111+ func createGoMod (tempDir , appDirName , goVersion string , opts BuildOpts , verbose bool ) error {
112+ modulePath := fmt .Sprintf ("tsunami/app/%s" , appDirName )
113+
114+ // Create new modfile
115+ modFile := & modfile.File {}
116+ if err := modFile .AddModuleStmt (modulePath ); err != nil {
117+ return fmt .Errorf ("failed to add module statement: %w" , err )
118+ }
119+
120+ if err := modFile .AddGoStmt (goVersion ); err != nil {
121+ return fmt .Errorf ("failed to add go version: %w" , err )
122+ }
123+
124+ // Add requirement for tsunami SDK
125+ if err := modFile .AddRequire ("github.com/wavetermdev/waveterm/tsunami" , "v0.0.0" ); err != nil {
126+ return fmt .Errorf ("failed to add require directive: %w" , err )
127+ }
128+
129+ // Add replace directive for tsunami SDK
130+ if err := modFile .AddReplace ("github.com/wavetermdev/waveterm/tsunami" , "" , opts .SdkReplacePath , "" ); err != nil {
131+ return fmt .Errorf ("failed to add replace directive: %w" , err )
132+ }
133+
134+ // Format and write the file
135+ modFile .Cleanup ()
136+ goModContent , err := modFile .Format ()
137+ if err != nil {
138+ return fmt .Errorf ("failed to format go.mod: %w" , err )
139+ }
140+
141+ goModPath := filepath .Join (tempDir , "go.mod" )
142+ if err := os .WriteFile (goModPath , goModContent , 0644 ); err != nil {
143+ return fmt .Errorf ("failed to write go.mod file: %w" , err )
144+ }
145+
146+ if verbose {
147+ log .Printf ("Created go.mod with module path: %s" , modulePath )
148+ log .Printf ("Added require: github.com/wavetermdev/waveterm/tsunami v0.0.0" )
149+ log .Printf ("Added replace directive: github.com/wavetermdev/waveterm/tsunami => %s" , opts .SdkReplacePath )
150+ }
151+
152+ // Run go mod tidy to clean up dependencies
153+ tidyCmd := exec .Command ("go" , "mod" , "tidy" )
154+ tidyCmd .Dir = tempDir
155+
156+ if verbose {
157+ log .Printf ("Running go mod tidy in %s" , tempDir )
158+ }
159+
160+ output , err := tidyCmd .CombinedOutput ()
161+ if err != nil {
162+ return fmt .Errorf ("failed to run go mod tidy: %w\n Output: %s" , err , string (output ))
163+ }
164+
165+ if verbose {
166+ log .Printf ("go mod tidy output:\n %s" , string (output ))
167+ log .Printf ("Successfully ran go mod tidy" )
90168 }
91169
92170 return nil
@@ -171,7 +249,8 @@ func verifyDistPath(distPath string) error {
171249}
172250
173251func TsunamiBuild (opts BuildOpts ) error {
174- if err := verifyEnvironment (opts .Verbose ); err != nil {
252+ buildEnv , err := verifyEnvironment (opts .Verbose )
253+ if err != nil {
175254 return err
176255 }
177256
@@ -224,30 +303,78 @@ func TsunamiBuild(opts BuildOpts) error {
224303 return fmt .Errorf ("failed to copy main.go.tmpl: %w" , err )
225304 }
226305
306+ // Create go.mod file
307+ appDirName := filepath .Base (opts .Dir )
308+ if err := createGoMod (tempDir , appDirName , buildEnv .GoVersion , opts , opts .Verbose ); err != nil {
309+ return fmt .Errorf ("failed to create go.mod: %w" , err )
310+ }
311+
227312 // Generate Tailwind CSS
228313 if err := generateAppTailwindCss (opts .DistPath , tempDir , opts .Verbose ); err != nil {
229314 return fmt .Errorf ("failed to generate tailwind css: %w" , err )
230315 }
231316
317+ // Build the Go application
318+ if err := runGoBuild (tempDir , opts .Verbose ); err != nil {
319+ return fmt .Errorf ("failed to build application: %w" , err )
320+ }
321+
322+ return nil
323+ }
324+
325+ func runGoBuild (tempDir string , verbose bool ) error {
326+ binDir := filepath .Join (tempDir , "bin" )
327+ if err := os .MkdirAll (binDir , 0755 ); err != nil {
328+ return fmt .Errorf ("failed to create bin directory: %w" , err )
329+ }
330+
331+ goFiles , err := listGoFilesInDir (tempDir )
332+ if err != nil {
333+ return fmt .Errorf ("failed to list go files: %w" , err )
334+ }
335+
336+ if len (goFiles ) == 0 {
337+ return fmt .Errorf ("no .go files found in %s" , tempDir )
338+ }
339+
340+ // Build command with explicit go files
341+ args := append ([]string {"build" , "-o" , "bin/app" }, goFiles ... )
342+ buildCmd := exec .Command ("go" , args ... )
343+ buildCmd .Dir = tempDir
344+
345+ if verbose {
346+ log .Printf ("Running: %s in %s" , strings .Join (buildCmd .Args , " " ), tempDir )
347+ buildCmd .Stdout = os .Stdout
348+ buildCmd .Stderr = os .Stderr
349+ }
350+
351+ if err := buildCmd .Run (); err != nil {
352+ return fmt .Errorf ("failed to build application: %w" , err )
353+ }
354+
355+ if verbose {
356+ log .Printf ("Application built successfully at %s" , filepath .Join (binDir , "app" ))
357+ }
358+
232359 return nil
233360}
234361
235362func generateAppTailwindCss (distPath , tempDir string , verbose bool ) error {
236363 tailwindInput := filepath .Join (distPath , "templates" , "tailwind.css" )
237364 tailwindOutput := filepath .Join (tempDir , "static" , "tw.css" )
238365 contentGlob := filepath .Join (tempDir , "*.go" )
239-
366+
240367 tailwindCmd := exec .Command ("npx" , "@tailwindcss/cli" ,
241368 "-i" , tailwindInput ,
242369 "-o" , tailwindOutput ,
243370 "--content" , contentGlob )
244-
371+
245372 if verbose {
246373 log .Printf ("Running: %s" , strings .Join (tailwindCmd .Args , " " ))
247374 tailwindCmd .Stdout = os .Stdout
248375 tailwindCmd .Stderr = os .Stderr
249376 }
250-
377+
251378 if err := tailwindCmd .Run (); err != nil {
252379 return fmt .Errorf ("failed to run tailwind command: %w" , err )
253380 }
0 commit comments