Skip to content

Commit bea4592

Browse files
committed
progress on build, still fighting tailwind
1 parent 35a98fe commit bea4592

6 files changed

Lines changed: 149 additions & 55 deletions

File tree

tsunami/app/defaultclient.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ package app
55

66
import (
77
"context"
8-
"embed"
8+
"io/fs"
99
"net/http"
1010

1111
"github.com/wavetermdev/waveterm/tsunami/vdom"
1212
)
1313

1414
var defaultClient = MakeClient(AppOpts{})
15-
var assetsFS *embed.FS
16-
var staticFS *embed.FS
15+
var assetsFS fs.FS
16+
var staticFS fs.FS
1717
var manifestFile *FileHandlerOption
1818

1919
// Default client methods that operate on the global defaultClient
@@ -66,12 +66,12 @@ func RegisterFileHandler(path string, option FileHandlerOption) {
6666
defaultClient.RegisterFileHandler(path, option)
6767
}
6868

69-
func RegisterAssetsFS(fs embed.FS) {
70-
assetsFS = &fs
69+
func RegisterAssetsFS(filesystem fs.FS) {
70+
assetsFS = filesystem
7171
}
7272

73-
func RegisterStaticFS(fs embed.FS) {
74-
staticFS = &fs
73+
func RegisterStaticFS(filesystem fs.FS) {
74+
staticFS = filesystem
7575
}
7676

7777
func RegisterManifestFile(option FileHandlerOption) {

tsunami/app/serverhandlers.go

Lines changed: 79 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
package app
55

66
import (
7-
"embed"
87
"encoding/json"
98
"fmt"
109
"io"
10+
"io/fs"
1111
"log"
1212
"mime"
1313
"net/http"
@@ -27,8 +27,8 @@ func init() {
2727
}
2828

2929
type HandlerOpts struct {
30-
AssetsFS *embed.FS
31-
StaticFS *embed.FS
30+
AssetsFS fs.FS
31+
StaticFS fs.FS
3232
ManifestFile *FileHandlerOption
3333
}
3434

@@ -316,8 +316,39 @@ func (h *HTTPHandlers) handleSSE(w http.ResponseWriter, r *http.Request) {
316316
}
317317
}
318318

319-
func (h *HTTPHandlers) handleStaticFiles(embeddedFS *embed.FS) http.HandlerFunc {
320-
// Create a file server from the embedded FS
319+
// serveFileDirectly serves a file directly from an embed.FS to avoid redirect loops
320+
// when serving directory paths that end with "/"
321+
func serveFileDirectly(w http.ResponseWriter, r *http.Request, embeddedFS fs.FS, requestPath, fileName string) bool {
322+
if !strings.HasSuffix(requestPath, "/") {
323+
return false
324+
}
325+
326+
// Try to serve the specified file from that directory
327+
var filePath string
328+
if requestPath == "/" {
329+
filePath = fileName
330+
} else {
331+
filePath = strings.TrimPrefix(requestPath, "/") + fileName
332+
}
333+
334+
file, err := embeddedFS.Open(filePath)
335+
if err != nil {
336+
return false
337+
}
338+
defer file.Close()
339+
340+
// Get file info for modification time
341+
fileInfo, err := file.Stat()
342+
if err != nil {
343+
return false
344+
}
345+
346+
// Serve the file directly with proper mod time
347+
http.ServeContent(w, r, fileName, fileInfo.ModTime(), file.(io.ReadSeeker))
348+
return true
349+
}
350+
351+
func (h *HTTPHandlers) handleStaticFiles(embeddedFS fs.FS) http.HandlerFunc {
321352
fileServer := http.FileServer(http.FS(embeddedFS))
322353

323354
return func(w http.ResponseWriter, r *http.Request) {
@@ -328,18 +359,26 @@ func (h *HTTPHandlers) handleStaticFiles(embeddedFS *embed.FS) http.HandlerFunc
328359
}
329360
}()
330361

331-
// Skip if this is an API or files request (already handled by other handlers)
332-
if strings.HasPrefix(r.URL.Path, "/api/") || strings.HasPrefix(r.URL.Path, "/files/") {
362+
// Skip if this is an API, files, or static request (already handled by other handlers)
363+
if strings.HasPrefix(r.URL.Path, "/api/") || strings.HasPrefix(r.URL.Path, "/files/") || strings.HasPrefix(r.URL.Path, "/static/") {
333364
http.NotFound(w, r)
334365
return
335366
}
336367

337-
// Handle root "/" => "/index.html"
338-
if r.URL.Path == "/" {
339-
r.URL.Path = "/index.html"
368+
// Handle any path ending with "/" to avoid redirect loops
369+
if serveFileDirectly(w, r, embeddedFS, r.URL.Path, "index.html") {
370+
return
371+
}
372+
373+
// For other files, check if they exist before serving
374+
filePath := strings.TrimPrefix(r.URL.Path, "/")
375+
_, err := embeddedFS.Open(filePath)
376+
if err != nil {
377+
http.NotFound(w, r)
378+
return
340379
}
341380

342-
// Serve the file using Go's file server
381+
// Serve the file using the file server
343382
fileServer.ServeHTTP(w, r)
344383
}
345384
}
@@ -367,10 +406,7 @@ func (h *HTTPHandlers) handleManifest(manifestFile *FileHandlerOption) http.Hand
367406
}
368407
}
369408

370-
func (h *HTTPHandlers) handleStaticPathFiles(staticFS *embed.FS) http.HandlerFunc {
371-
// Create a file server from the embedded FS
372-
fileServer := http.FileServer(http.FS(staticFS))
373-
409+
func (h *HTTPHandlers) handleStaticPathFiles(staticFS fs.FS) http.HandlerFunc {
374410
return func(w http.ResponseWriter, r *http.Request) {
375411
defer func() {
376412
panicErr := util.PanicHandler("handleStaticPathFiles", recover())
@@ -380,12 +416,36 @@ func (h *HTTPHandlers) handleStaticPathFiles(staticFS *embed.FS) http.HandlerFun
380416
}()
381417

382418
// Strip /static/ prefix from the path
383-
r.URL.Path = strings.TrimPrefix(r.URL.Path, "/static")
384-
if r.URL.Path == "" {
385-
r.URL.Path = "/"
419+
filePath := strings.TrimPrefix(r.URL.Path, "/static/")
420+
if filePath == "" {
421+
// Handle requests to "/static/" directly
422+
if serveFileDirectly(w, r, staticFS, "/", "index.html") {
423+
return
424+
}
425+
http.NotFound(w, r)
426+
return
427+
}
428+
429+
// Handle directory paths ending with "/" to avoid redirect loops
430+
strippedPath := "/" + filePath
431+
if serveFileDirectly(w, r, staticFS, strippedPath, "index.html") {
432+
return
433+
}
434+
435+
// Check if file exists in staticFS
436+
_, err := staticFS.Open(filePath)
437+
if err != nil {
438+
http.NotFound(w, r)
439+
return
386440
}
387441

388-
// Serve the file using Go's file server
442+
// Create a file server and serve the file
443+
fileServer := http.FileServer(http.FS(staticFS))
444+
445+
// Temporarily modify the URL path for the file server
446+
originalPath := r.URL.Path
447+
r.URL.Path = "/" + filePath
389448
fileServer.ServeHTTP(w, r)
449+
r.URL.Path = originalPath
390450
}
391451
}

tsunami/app/tsunamiapp.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ func (c *Client) listenAndServe(ctx context.Context) error {
195195

196196
// Log the port we're listening on
197197
port := listener.Addr().(*net.TCPAddr).Port
198-
log.Printf("Wave app server listening on port %d", port)
198+
log.Printf("[tsunami] listening on port %d", port)
199199

200200
// Serve in a goroutine so we don't block
201201
go func() {

tsunami/build/build.go

Lines changed: 60 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type BuildOpts struct {
2222

2323
type BuildEnv struct {
2424
GoVersion string
25+
TempDir string
2526
}
2627

2728
func verifyEnvironment(verbose bool) (*BuildEnv, error) {
@@ -154,16 +155,16 @@ func createGoMod(tempDir, appDirName, goVersion string, opts BuildOpts, verbose
154155
tidyCmd.Dir = tempDir
155156

156157
if verbose {
157-
log.Printf("Running go mod tidy in %s", tempDir)
158+
log.Printf("Running go mod tidy")
159+
tidyCmd.Stdout = os.Stdout
160+
tidyCmd.Stderr = os.Stderr
158161
}
159162

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+
if err := tidyCmd.Run(); err != nil {
164+
return fmt.Errorf("failed to run go mod tidy: %w", err)
163165
}
164166

165167
if verbose {
166-
log.Printf("go mod tidy output:\n%s", string(output))
167168
log.Printf("Successfully ran go mod tidy")
168169
}
169170

@@ -248,26 +249,28 @@ func verifyDistPath(distPath string) error {
248249
return nil
249250
}
250251

251-
func TsunamiBuild(opts BuildOpts) error {
252+
func TsunamiBuild(opts BuildOpts) (*BuildEnv, error) {
252253
buildEnv, err := verifyEnvironment(opts.Verbose)
253254
if err != nil {
254-
return err
255+
return nil, err
255256
}
256257

257258
if err := verifyTsunamiDir(opts.Dir); err != nil {
258-
return err
259+
return nil, err
259260
}
260261

261262
if err := verifyDistPath(opts.DistPath); err != nil {
262-
return err
263+
return nil, err
263264
}
264265

265266
// Create temporary directory
266267
tempDir, err := os.MkdirTemp("", "tsunami-build-*")
267268
if err != nil {
268-
return fmt.Errorf("failed to create temp directory: %w", err)
269+
return nil, fmt.Errorf("failed to create temp directory: %w", err)
269270
}
270271

272+
buildEnv.TempDir = tempDir
273+
271274
log.Printf("Building tsunami app from %s\n", opts.Dir)
272275

273276
if opts.Verbose {
@@ -277,49 +280,55 @@ func TsunamiBuild(opts BuildOpts) error {
277280
// Copy all *.go files from the root directory
278281
goCount, err := copyGoFiles(opts.Dir, tempDir)
279282
if err != nil {
280-
return fmt.Errorf("failed to copy go files: %w", err)
283+
return nil, fmt.Errorf("failed to copy go files: %w", err)
281284
}
282285

283286
// Copy static directory
284287
staticCount, err := copyStaticDir(opts.Dir, tempDir)
285288
if err != nil {
286-
return fmt.Errorf("failed to copy static directory: %w", err)
289+
return nil, fmt.Errorf("failed to copy static directory: %w", err)
287290
}
288291

289292
// Create dist directory
290293
distDir := filepath.Join(tempDir, "dist")
291294
if err := os.MkdirAll(distDir, 0755); err != nil {
292-
return fmt.Errorf("failed to create dist directory: %w", err)
295+
return nil, fmt.Errorf("failed to create dist directory: %w", err)
296+
}
297+
298+
// Copy dist directory contents
299+
distCount, err := copyDirRecursive(opts.DistPath, distDir)
300+
if err != nil {
301+
return nil, fmt.Errorf("failed to copy dist directory: %w", err)
293302
}
294303

295304
if opts.Verbose {
296-
log.Printf("Copied %d go files, %d static files\n", goCount, staticCount)
305+
log.Printf("Copied %d go files, %d static files, %d dist files\n", goCount, staticCount, distCount)
297306
}
298307

299308
// Copy main.go.tmpl from dist/templates to temp dir as main-app.go
300309
mainTmplSrc := filepath.Join(opts.DistPath, "templates", "main.go.tmpl")
301310
mainTmplDest := filepath.Join(tempDir, "main-app.go")
302311
if err := copyFile(mainTmplSrc, mainTmplDest); err != nil {
303-
return fmt.Errorf("failed to copy main.go.tmpl: %w", err)
312+
return nil, fmt.Errorf("failed to copy main.go.tmpl: %w", err)
304313
}
305314

306315
// Create go.mod file
307316
appDirName := filepath.Base(opts.Dir)
308317
if err := createGoMod(tempDir, appDirName, buildEnv.GoVersion, opts, opts.Verbose); err != nil {
309-
return fmt.Errorf("failed to create go.mod: %w", err)
318+
return nil, fmt.Errorf("failed to create go.mod: %w", err)
310319
}
311320

312321
// Generate Tailwind CSS
313322
if err := generateAppTailwindCss(opts.DistPath, tempDir, opts.Verbose); err != nil {
314-
return fmt.Errorf("failed to generate tailwind css: %w", err)
323+
return nil, fmt.Errorf("failed to generate tailwind css: %w", err)
315324
}
316325

317326
// Build the Go application
318327
if err := runGoBuild(tempDir, opts.Verbose); err != nil {
319-
return fmt.Errorf("failed to build application: %w", err)
328+
return nil, fmt.Errorf("failed to build application: %w", err)
320329
}
321330

322-
return nil
331+
return buildEnv, nil
323332
}
324333

325334
func runGoBuild(tempDir string, verbose bool) error {
@@ -343,7 +352,7 @@ func runGoBuild(tempDir string, verbose bool) error {
343352
buildCmd.Dir = tempDir
344353

345354
if verbose {
346-
log.Printf("Running: %s in %s", strings.Join(buildCmd.Args, " "), tempDir)
355+
log.Printf("Running: %s", strings.Join(buildCmd.Args, " "))
347356
buildCmd.Stdout = os.Stdout
348357
buildCmd.Stderr = os.Stderr
349358
}
@@ -360,14 +369,19 @@ func runGoBuild(tempDir string, verbose bool) error {
360369
}
361370

362371
func generateAppTailwindCss(distPath, tempDir string, verbose bool) error {
363-
tailwindInput := filepath.Join(distPath, "templates", "tailwind.css")
372+
// Copy tailwind.css from dist/templates to temp dir
373+
tailwindSrc := filepath.Join(distPath, "templates", "tailwind.css")
374+
tailwindDest := filepath.Join(tempDir, "tailwind.css")
375+
if err := copyFile(tailwindSrc, tailwindDest); err != nil {
376+
return fmt.Errorf("failed to copy tailwind.css: %w", err)
377+
}
378+
364379
tailwindOutput := filepath.Join(tempDir, "static", "tw.css")
365-
contentGlob := filepath.Join(tempDir, "*.go")
366380

367381
tailwindCmd := exec.Command("npx", "@tailwindcss/cli",
368-
"-i", tailwindInput,
369-
"-o", tailwindOutput,
370-
"--content", contentGlob)
382+
"-i", "./tailwind.css",
383+
"-o", tailwindOutput)
384+
tailwindCmd.Dir = tempDir
371385

372386
if verbose {
373387
log.Printf("Running: %s", strings.Join(tailwindCmd.Args, " "))
@@ -429,9 +443,28 @@ func copyGoFiles(srcDir, destDir string) (int, error) {
429443
}
430444

431445
func TsunamiRun(opts BuildOpts) error {
432-
if err := TsunamiBuild(opts); err != nil {
446+
buildEnv, err := TsunamiBuild(opts)
447+
if err != nil {
433448
return err
434449
}
435450

436-
return fmt.Errorf("TsunamiRun not implemented yet")
451+
// Run the built application
452+
appPath := filepath.Join(buildEnv.TempDir, "bin", "app")
453+
runCmd := exec.Command(appPath)
454+
runCmd.Dir = buildEnv.TempDir
455+
456+
log.Printf("Running tsunami app from %s", opts.Dir)
457+
458+
runCmd.Stdin = os.Stdin
459+
if opts.Verbose {
460+
log.Printf("Executing: %s", appPath)
461+
runCmd.Stdout = os.Stdout
462+
runCmd.Stderr = os.Stderr
463+
}
464+
465+
if err := runCmd.Run(); err != nil {
466+
return fmt.Errorf("failed to run application: %w", err)
467+
}
468+
469+
return nil
437470
}

tsunami/cmd/main-tsunami.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ var buildCmd = &cobra.Command{
5959
if err := validateEnvironmentVars(&opts); err != nil {
6060
return err
6161
}
62-
if err := build.TsunamiBuild(opts); err != nil {
62+
if _, err := build.TsunamiBuild(opts); err != nil {
6363
return fmt.Errorf("build failed: %w", err)
6464
}
6565
return nil

0 commit comments

Comments
 (0)