Skip to content

Commit 35a98fe

Browse files
committed
getting closer with the build...
1 parent 7ae7cf8 commit 35a98fe

5 files changed

Lines changed: 210 additions & 52 deletions

File tree

tsunami/build/build.go

Lines changed: 149 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,33 @@ import (
99
"regexp"
1010
"strconv"
1111
"strings"
12+
13+
"golang.org/x/mod/modfile"
1214
)
1315

1416
type 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\nOutput: %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

173251
func 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

235362
func 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
}

tsunami/build/buildutil.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,19 @@ func copyFile(srcPath, destPath string) error {
114114
// Set the same mode as source file
115115
return os.Chmod(destPath, srcInfo.Mode())
116116
}
117+
118+
func listGoFilesInDir(dirPath string) ([]string, error) {
119+
entries, err := os.ReadDir(dirPath)
120+
if err != nil {
121+
return nil, fmt.Errorf("failed to read directory %s: %w", dirPath, err)
122+
}
123+
124+
var goFiles []string
125+
for _, entry := range entries {
126+
if !entry.IsDir() && filepath.Ext(entry.Name()) == ".go" {
127+
goFiles = append(goFiles, entry.Name())
128+
}
129+
}
130+
131+
return goFiles, nil
132+
}

tsunami/cmd/main-tsunami.go

Lines changed: 42 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -28,51 +28,63 @@ var versionCmd = &cobra.Command{
2828
},
2929
}
3030

31+
func validateEnvironmentVars(opts *build.BuildOpts) error {
32+
distPath := os.Getenv("TSUNAMI_DISTPATH")
33+
if distPath == "" {
34+
return fmt.Errorf("TSUNAMI_DISTPATH environment variable must be set")
35+
}
36+
37+
sdkReplacePath := os.Getenv("TSUNAMI_SDKREPLACEPATH")
38+
if sdkReplacePath == "" {
39+
return fmt.Errorf("TSUNAMI_SDKREPLACEPATH environment variable must be set")
40+
}
41+
42+
opts.DistPath = distPath
43+
opts.SdkReplacePath = sdkReplacePath
44+
return nil
45+
}
46+
3147
var buildCmd = &cobra.Command{
32-
Use: "build [directory]",
33-
Short: "Build a Tsunami application",
34-
Long: `Build a Tsunami application from the specified directory.`,
35-
Args: cobra.ExactArgs(1),
36-
Run: func(cmd *cobra.Command, args []string) {
48+
Use: "build [directory]",
49+
Short: "Build a Tsunami application",
50+
Long: `Build a Tsunami application from the specified directory.`,
51+
Args: cobra.ExactArgs(1),
52+
SilenceUsage: true,
53+
RunE: func(cmd *cobra.Command, args []string) error {
3754
verbose, _ := cmd.Flags().GetBool("verbose")
38-
distPath := os.Getenv("TSUNAMI_DISTPATH")
39-
if distPath == "" {
40-
fmt.Printf("Error: TSUNAMI_DISTPATH environment variable must be set\n")
41-
os.Exit(1)
42-
}
4355
opts := build.BuildOpts{
44-
Dir: args[0],
45-
Verbose: verbose,
46-
DistPath: distPath,
56+
Dir: args[0],
57+
Verbose: verbose,
58+
}
59+
if err := validateEnvironmentVars(&opts); err != nil {
60+
return err
4761
}
4862
if err := build.TsunamiBuild(opts); err != nil {
49-
fmt.Printf("Build failed: %v\n", err)
50-
os.Exit(1)
63+
return fmt.Errorf("build failed: %w", err)
5164
}
65+
return nil
5266
},
5367
}
5468

5569
var runCmd = &cobra.Command{
56-
Use: "run [directory]",
57-
Short: "Build and run a Tsunami application",
58-
Long: `Build and run a Tsunami application from the specified directory.`,
59-
Args: cobra.ExactArgs(1),
60-
Run: func(cmd *cobra.Command, args []string) {
70+
Use: "run [directory]",
71+
Short: "Build and run a Tsunami application",
72+
Long: `Build and run a Tsunami application from the specified directory.`,
73+
Args: cobra.ExactArgs(1),
74+
SilenceUsage: true,
75+
RunE: func(cmd *cobra.Command, args []string) error {
6176
verbose, _ := cmd.Flags().GetBool("verbose")
62-
distPath := os.Getenv("TSUNAMI_DISTPATH")
63-
if distPath == "" {
64-
fmt.Printf("Error: TSUNAMI_DISTPATH environment variable must be set\n")
65-
os.Exit(1)
66-
}
6777
opts := build.BuildOpts{
68-
Dir: args[0],
69-
Verbose: verbose,
70-
DistPath: distPath,
78+
Dir: args[0],
79+
Verbose: verbose,
80+
}
81+
if err := validateEnvironmentVars(&opts); err != nil {
82+
return err
7183
}
7284
if err := build.TsunamiRun(opts); err != nil {
73-
fmt.Printf("Run failed: %v\n", err)
74-
os.Exit(1)
85+
return fmt.Errorf("run failed: %w", err)
7586
}
87+
return nil
7688
},
7789
}
7890

tsunami/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
github.com/gorilla/mux v1.8.1
88
github.com/spf13/cobra v1.10.1
99
github.com/wavetermdev/htmltoken v0.2.0
10+
golang.org/x/mod v0.27.0
1011
)
1112

1213
require (

tsunami/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
1212
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
1313
github.com/wavetermdev/htmltoken v0.2.0 h1:sFVPPemlDv7/jg7n4Hx1AEF2m9MVAFjFpELWfhi/DlM=
1414
github.com/wavetermdev/htmltoken v0.2.0/go.mod h1:5FM0XV6zNYiNza2iaTcFGj+hnMtgqumFHO31Z8euquk=
15+
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
16+
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
1517
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
1618
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
1719
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

0 commit comments

Comments
 (0)