Files
insertr/insertr-cli/cmd/servedev.go
Joakim ae9d8e4058 refactor: implement script tag approach for library inclusion
- Add script tags to demo-site HTML files for manual development
- Disable CLI inline script injection to prevent duplicate scripts
- Add library serving endpoints to servedev command
- Update build process to auto-copy library to demo-site
- Add CDN URL helpers for future production deployment
- Update .gitignore for generated demo-site files

Fixes .insertr-gate authentication for manual npm run serve workflow
while maintaining clean separation between CLI and manual setups.
2025-09-07 18:28:34 +02:00

190 lines
5.7 KiB
Go

package cmd
import (
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"github.com/insertr/cli/pkg/content"
)
var servedevCmd = &cobra.Command{
Use: "servedev",
Short: "Development server that parses and serves enhanced HTML files",
Long: `Servedev starts a development HTTP server that automatically parses HTML files
for insertr elements and serves the enhanced content. Perfect for development workflow
with live rebuilds via Air.`,
Run: runServedev,
}
var (
inputDir string
port int
useMockContent bool
devSiteID string
)
func init() {
rootCmd.AddCommand(servedevCmd)
servedevCmd.Flags().StringVarP(&inputDir, "input", "i", ".", "Input directory to serve")
servedevCmd.Flags().IntVarP(&port, "port", "p", 3000, "Port to serve on")
servedevCmd.Flags().BoolVar(&useMockContent, "mock", true, "Use mock content for development")
servedevCmd.Flags().StringVarP(&devSiteID, "site-id", "s", "demo", "Site ID for content lookup")
}
func runServedev(cmd *cobra.Command, args []string) {
// Resolve absolute path for input directory
absInputDir, err := filepath.Abs(inputDir)
if err != nil {
log.Fatalf("Error resolving input directory: %v", err)
}
// Check if input directory exists
if _, err := os.Stat(absInputDir); os.IsNotExist(err) {
log.Fatalf("Input directory does not exist: %s", absInputDir)
}
// Create content client
var client content.ContentClient
if useMockContent {
fmt.Printf("🧪 Using mock content for development\n")
client = content.NewMockClient()
} else {
// For now, default to mock if no API URL provided
fmt.Printf("🧪 Using mock content for development (no API configured)\n")
client = content.NewMockClient()
}
fmt.Printf("🚀 Starting development server with content enhancement...\n")
fmt.Printf("📁 Serving directory: %s\n", absInputDir)
fmt.Printf("🌐 Server running at: http://localhost:%d\n", port)
fmt.Printf("🏷️ Site ID: %s\n", devSiteID)
fmt.Printf("🔄 Manually refresh browser to see changes\n\n")
// Create enhanced file server
fileServer := http.FileServer(&enhancedFileSystem{
fs: http.Dir(absInputDir),
dir: absInputDir,
enhancer: content.NewEnhancer(client, devSiteID),
})
// Handle editor assets
http.HandleFunc("/_insertr/", func(w http.ResponseWriter, r *http.Request) {
assetPath := strings.TrimPrefix(r.URL.Path, "/_insertr/")
serveEditorAsset(w, r, assetPath)
})
// Handle insertr library files
http.HandleFunc("/insertr/", func(w http.ResponseWriter, r *http.Request) {
assetPath := strings.TrimPrefix(r.URL.Path, "/insertr/")
serveLibraryAsset(w, r, assetPath)
})
// Handle all other requests with our enhanced file server
http.Handle("/", fileServer)
// Start server
addr := fmt.Sprintf(":%d", port)
log.Fatal(http.ListenAndServe(addr, nil))
}
// serveEditorAsset serves editor JavaScript and CSS files
func serveEditorAsset(w http.ResponseWriter, r *http.Request, assetPath string) {
// Get the path to the CLI binary directory
execPath, err := os.Executable()
if err != nil {
http.NotFound(w, r)
return
}
// Look for assets relative to the CLI binary (for built version)
assetsDir := filepath.Join(filepath.Dir(execPath), "assets", "editor")
assetFile := filepath.Join(assetsDir, assetPath)
// If not found, look for assets relative to source (for development)
if _, err := os.Stat(assetFile); os.IsNotExist(err) {
// Assume we're running from source
cwd, _ := os.Getwd()
assetsDir = filepath.Join(cwd, "assets", "editor")
assetFile = filepath.Join(assetsDir, assetPath)
}
// Set appropriate content type
if strings.HasSuffix(assetPath, ".js") {
w.Header().Set("Content-Type", "application/javascript")
} else if strings.HasSuffix(assetPath, ".css") {
w.Header().Set("Content-Type", "text/css")
}
// Serve the file
http.ServeFile(w, r, assetFile)
}
// serveLibraryAsset serves the insertr library files from embedded assets
func serveLibraryAsset(w http.ResponseWriter, r *http.Request, assetPath string) {
w.Header().Set("Content-Type", "application/javascript")
var script string
switch assetPath {
case "insertr.js":
script = content.GetLibraryScript(false)
case "insertr.min.js":
script = content.GetLibraryScript(true)
default:
http.NotFound(w, r)
return
}
w.Write([]byte(script))
}
// enhancedFileSystem wraps http.FileSystem to provide enhanced HTML serving
type enhancedFileSystem struct {
fs http.FileSystem
dir string
enhancer *content.Enhancer
}
func (efs *enhancedFileSystem) Open(name string) (http.File, error) {
// For HTML files, enhance them on-the-fly
if strings.HasSuffix(name, ".html") {
fmt.Printf("📄 Enhancing HTML: %s\n", name)
return efs.serveEnhancedHTML(name)
}
// For non-HTML files, serve as-is
return efs.fs.Open(name)
}
// serveEnhancedHTML enhances an HTML file and returns it as an http.File
func (efs *enhancedFileSystem) serveEnhancedHTML(name string) (http.File, error) {
// Get the full file path
inputPath := filepath.Join(efs.dir, name)
// Create a temporary output path (in-memory would be better, but this is simpler for now)
tempDir := filepath.Join(os.TempDir(), "insertr-dev")
outputPath := filepath.Join(tempDir, name)
// Ensure temp directory exists
if err := os.MkdirAll(filepath.Dir(outputPath), 0755); err != nil {
fmt.Printf("⚠️ Failed to create temp directory: %v\n", err)
return efs.fs.Open(name) // Fallback to original file
}
// Enhance the file
if err := efs.enhancer.EnhanceFile(inputPath, outputPath); err != nil {
fmt.Printf("⚠️ Enhancement failed for %s: %v\n", name, err)
return efs.fs.Open(name) // Fallback to original file
}
// Serve the enhanced file
tempFS := http.Dir(tempDir)
return tempFS.Open(name)
}