From 4c1523d81b758d42aa116e10c5f59d4337daa1c4 Mon Sep 17 00:00:00 2001 From: Evan Reichard Date: Tue, 28 Apr 2026 22:09:19 -0400 Subject: [PATCH] fix: build --- Dockerfile | 2 +- Makefile | 22 +++++++++++++++++----- README.md | 1 + backend/cmd/config.go | 12 ++++++++++++ backend/cmd/main.go | 4 +++- backend/internal/server/server.go | 17 +++++++++++------ flake.nix | 1 + frontend/package.json | 1 + frontend/src/components/imageManager.ts | 20 +++++++++++++------- 9 files changed, 60 insertions(+), 20 deletions(-) diff --git a/Dockerfile b/Dockerfile index 74d5a1b..56dac25 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ COPY backend/go.mod backend/go.sum ./ RUN go mod download COPY backend/ ./ COPY --from=frontend-builder /app/frontend/public/ ./web/static/ -RUN go build -ldflags="-w -s" -o aethera ./cmd +RUN find ./web/static -type d -empty -delete && go build -ldflags="-w -s" -o aethera ./cmd # Stage 3: Minimal Runtime FROM alpine:3.21 diff --git a/Makefile b/Makefile index 6d7317c..369e4ed 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,22 @@ -.PHONY: all frontend backend clean dev docker docker-run tests +.PHONY: all frontend backend clean dev docker docker-run tests check-static all: frontend backend frontend: + rm -rf frontend/public/dist cd frontend && bun run build + rm -rf backend/web/static mkdir -p backend/web/static - cp frontend/public/index.html backend/web/static/ 2>/dev/null || true - cp -r frontend/public/pages backend/web/static/ 2>/dev/null || true + cp -R frontend/public/. backend/web/static/ + find backend/web/static -type d -empty -delete + touch backend/web/static/.gitkeep -backend: +check-static: + @test -f backend/web/static/index.html || (echo "missing backend/web/static/index.html; run 'make frontend' first" && exit 1) + @test -f backend/web/static/dist/main.js || (echo "missing backend/web/static/dist/main.js; run 'make frontend' first" && exit 1) + @test -f backend/web/static/dist/styles.css || (echo "missing backend/web/static/dist/styles.css; run 'make frontend' first" && exit 1) + +backend: check-static cd backend && go build -o ./dist/aethera ./cmd clean: @@ -17,7 +25,11 @@ clean: rm -rf backend/web/static dev: - cd backend && go run ./cmd --listen 0.0.0.0 & + rm -rf frontend/public/dist + cd frontend && bun run build + cd backend && AETHERA_STATIC_DIR=../frontend/public go run ./cmd --listen 0.0.0.0 & \ + backend_pid=$$!; \ + trap 'kill $$backend_pid' INT TERM EXIT; \ cd frontend && bun run dev docker: diff --git a/README.md b/README.md index 5da12dd..674615e 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ Open your browser and navigate to the URL to begin using Aethera. You can customize the server behavior with these command-line flags: - `--data-dir`: Directory for storing generated images (default: `data`) +- `--static-dir`: Directory to serve frontend files from instead of embedded assets (useful for development) - `--listen`: Address to listen on (default: `localhost`) - `--port`: Port to listen on (default: `8080`) diff --git a/backend/cmd/config.go b/backend/cmd/config.go index 5be1511..c3dfcc5 100644 --- a/backend/cmd/config.go +++ b/backend/cmd/config.go @@ -13,6 +13,7 @@ type cliParams struct { ListenAddr string ListenPort int DataDir string + StaticDir string SettingsFile string } @@ -41,5 +42,16 @@ func (p *cliParams) Validate() error { return fmt.Errorf("failed to create images directory: %w", err) } + // Validate Static Directory + if p.StaticDir != "" { + info, err := os.Stat(p.StaticDir) + if err != nil { + return fmt.Errorf("failed to access static directory: %w", err) + } + if !info.IsDir() { + return fmt.Errorf("static directory is not a directory: %s", p.StaticDir) + } + } + return nil } diff --git a/backend/cmd/main.go b/backend/cmd/main.go index 938135d..f782a23 100644 --- a/backend/cmd/main.go +++ b/backend/cmd/main.go @@ -16,12 +16,14 @@ var ( ListenAddr: getEnvOrDefault("LISTEN", "localhost"), ListenPort: getEnvIntOrDefault("PORT", 8080), DataDir: getEnvOrDefault("DATA_DIR", "./data"), + StaticDir: getEnvOrDefault("STATIC_DIR", ""), } rootCmd = &cobra.Command{Use: "aethera"} ) func init() { rootCmd.PersistentFlags().StringVar(¶ms.DataDir, "data-dir", params.DataDir, "Directory to store generated images (env: AETHERA_DATA_DIR)") + rootCmd.PersistentFlags().StringVar(¶ms.StaticDir, "static-dir", params.StaticDir, "Directory to serve static frontend files from instead of embedded assets (env: AETHERA_STATIC_DIR)") rootCmd.PersistentFlags().StringVar(¶ms.ListenAddr, "listen", params.ListenAddr, "Address to listen on (env: AETHERA_LISTEN)") rootCmd.PersistentFlags().IntVar(¶ms.ListenPort, "port", params.ListenPort, "Port to listen on (env: AETHERA_PORT)") } @@ -40,7 +42,7 @@ func main() { // Start Server rootCmd.Run = func(cmd *cobra.Command, args []string) { - server.StartServer(fileStore, params.DataDir, params.ListenAddr, params.ListenPort) + server.StartServer(fileStore, params.DataDir, params.StaticDir, params.ListenAddr, params.ListenPort) } if err := rootCmd.Execute(); err != nil { diff --git a/backend/internal/server/server.go b/backend/internal/server/server.go index 9c0c9de..2eb12da 100644 --- a/backend/internal/server/server.go +++ b/backend/internal/server/server.go @@ -13,19 +13,24 @@ import ( "reichard.io/aethera/web" ) -func StartServer(settingsStore store.Store, dataDir, listenAddress string, listenPort int) { +func StartServer(settingsStore store.Store, dataDir, staticDir, listenAddress string, listenPort int) { mux := http.NewServeMux() // Create API Instance - use settingsStore as the unified store for both settings and chat logger := logrus.New() api := api.New(settingsStore, dataDir, logger) - // Serve embedded static assets - staticFS, err := fs.Sub(web.Assets, "static") - if err != nil { - logrus.Fatal("Failed to create static filesystem: ", err) + // Serve Static Assets + if staticDir != "" { + logrus.Infof("Serving static assets from directory: %s", staticDir) + mux.Handle("GET /", http.FileServer(http.Dir(staticDir))) + } else { + staticFS, err := fs.Sub(web.Assets, "static") + if err != nil { + logrus.Fatal("Failed to create static filesystem: ", err) + } + mux.Handle("GET /", http.FileServer(http.FS(staticFS))) } - mux.Handle("GET /", http.FileServer(http.FS(staticFS))) // Serve Generated Data genFS := http.FileServer(http.Dir(path.Join(dataDir, "generated"))) diff --git a/flake.nix b/flake.nix index 41714d3..8f66f23 100644 --- a/flake.nix +++ b/flake.nix @@ -32,6 +32,7 @@ # Frontend bun + typescript-language-server watchman ]; diff --git a/frontend/package.json b/frontend/package.json index 3ecc325..abb9d61 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,7 @@ { "name": "aethera", "private": true, + "type": "module", "scripts": { "dev": "mkdir -p public/dist && bun build src/main.ts --outdir public/dist --target browser --watch & bunx tailwindcss -i styles.css -o public/dist/styles.css --watch", "build": "bun build src/main.ts --outdir public/dist --target browser && bunx tailwindcss -i styles.css -o public/dist/styles.css --minify", diff --git a/frontend/src/components/imageManager.ts b/frontend/src/components/imageManager.ts index 319d864..3a56511 100644 --- a/frontend/src/components/imageManager.ts +++ b/frontend/src/components/imageManager.ts @@ -6,7 +6,7 @@ import { getModels, getSettings, } from '../client'; -import { ImageRecord } from '../types'; +import { GenerateImageRequest, ImageRecord } from '../types'; import { applyFilter } from '../utils'; // Constants @@ -26,6 +26,12 @@ interface StoredSettings { } // Utilities +const errorMessage = (err: unknown): string => { + if (err instanceof Error) return err.message; + if (typeof err === 'string') return err; + return 'An unexpected error occurred'; +}; + const fileToDataURL = (file: File): Promise => new Promise((resolve, reject) => { const reader = new FileReader(); @@ -252,8 +258,8 @@ Alpine.data('imageGenerator', () => { }); }, - async buildRequestData() { - const requestData: any = { + async buildRequestData(): Promise { + const requestData: GenerateImageRequest = { prompt: this.prompt, n: parseInt(this.n.toString()), seed: parseInt(this.seed.toString()), @@ -281,8 +287,8 @@ Alpine.data('imageGenerator', () => { const requestData = await this.buildRequestData(); const data = await generateImage(requestData); this.generatedImages.unshift(...data); - } catch (err: any) { - this.error = err; + } catch (err) { + this.error = errorMessage(err); } finally { this.loading = false; } @@ -294,8 +300,8 @@ Alpine.data('imageGenerator', () => { this.generatedImages = this.generatedImages.filter( (img: ImageRecord) => img.name !== filename, ); - } catch (err: any) { - this.error = err; + } catch (err) { + this.error = errorMessage(err); } },