Implement complete content injection and enhancement pipeline

- Add content API client with HTTP and mock implementations
- Implement HTML content injection with database content replacement
- Create enhance command for build-time content injection
- Integrate enhancement with servedev for live development workflow
- Add editor asset injection and serving (/_insertr/ endpoints)
- Support on-the-fly HTML enhancement during development
- Enable complete 'Tailwind of CMS' workflow: parse → inject → serve
This commit is contained in:
2025-09-03 12:35:54 +02:00
parent 1f97acc1bf
commit 4407f84bbc
8 changed files with 1017 additions and 20 deletions

View File

@@ -0,0 +1,76 @@
package cmd
import (
"fmt"
"log"
"os"
"github.com/spf13/cobra"
"github.com/insertr/cli/pkg/content"
)
var enhanceCmd = &cobra.Command{
Use: "enhance [input-dir]",
Short: "Enhance HTML files by injecting content from database",
Long: `Enhance processes HTML files and injects latest content from the database
while adding editing capabilities. This is the core build-time enhancement
process that transforms static HTML into an editable CMS.`,
Args: cobra.ExactArgs(1),
Run: runEnhance,
}
var (
outputDir string
apiURL string
apiKey string
siteID string
mockContent bool
)
func init() {
rootCmd.AddCommand(enhanceCmd)
enhanceCmd.Flags().StringVarP(&outputDir, "output", "o", "./dist", "Output directory for enhanced files")
enhanceCmd.Flags().StringVar(&apiURL, "api-url", "", "Content API URL")
enhanceCmd.Flags().StringVar(&apiKey, "api-key", "", "API key for authentication")
enhanceCmd.Flags().StringVarP(&siteID, "site-id", "s", "demo", "Site ID for content lookup")
enhanceCmd.Flags().BoolVar(&mockContent, "mock", true, "Use mock content for development")
}
func runEnhance(cmd *cobra.Command, args []string) {
inputDir := args[0]
// Validate input directory
if _, err := os.Stat(inputDir); os.IsNotExist(err) {
log.Fatalf("Input directory does not exist: %s", inputDir)
}
// Create content client
var client content.ContentClient
if mockContent {
fmt.Printf("🧪 Using mock content for development\n")
client = content.NewMockClient()
} else {
if apiURL == "" {
log.Fatal("API URL required when not using mock content (use --api-url)")
}
fmt.Printf("🌐 Using content API: %s\n", apiURL)
client = content.NewHTTPClient(apiURL, apiKey)
}
// Create enhancer
enhancer := content.NewEnhancer(client, siteID)
fmt.Printf("🚀 Starting enhancement process...\n")
fmt.Printf("📁 Input: %s\n", inputDir)
fmt.Printf("📁 Output: %s\n", outputDir)
fmt.Printf("🏷️ Site ID: %s\n\n", siteID)
// Enhance directory
if err := enhancer.EnhanceDirectory(inputDir, outputDir); err != nil {
log.Fatalf("Enhancement failed: %v", err)
}
fmt.Printf("\n✅ Enhancement complete! Enhanced files available in: %s\n", outputDir)
}

View File

@@ -9,6 +9,8 @@ import (
"strings"
"github.com/spf13/cobra"
"github.com/insertr/cli/pkg/content"
)
var servedevCmd = &cobra.Command{
@@ -21,8 +23,10 @@ with live rebuilds via Air.`,
}
var (
inputDir string
port int
inputDir string
port int
useMockContent bool
devSiteID string
)
func init() {
@@ -30,6 +34,8 @@ func init() {
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) {
@@ -44,18 +50,37 @@ func runServedev(cmd *cobra.Command, args []string) {
log.Fatalf("Input directory does not exist: %s", absInputDir)
}
fmt.Printf("🚀 Starting development server...\n")
// 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 file server
// Create enhanced file server
fileServer := http.FileServer(&enhancedFileSystem{
fs: http.Dir(absInputDir),
dir: absInputDir,
fs: http.Dir(absInputDir),
dir: absInputDir,
enhancer: content.NewEnhancer(client, devSiteID),
})
// Handle all requests with our enhanced file server
// 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 all other requests with our enhanced file server
http.Handle("/", fileServer)
// Start server
@@ -63,25 +88,78 @@ func runServedev(cmd *cobra.Command, args []string) {
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)
}
// enhancedFileSystem wraps http.FileSystem to provide enhanced HTML serving
type enhancedFileSystem struct {
fs http.FileSystem
dir string
fs http.FileSystem
dir string
enhancer *content.Enhancer
}
func (efs *enhancedFileSystem) Open(name string) (http.File, error) {
file, err := efs.fs.Open(name)
if err != nil {
return nil, err
}
// For HTML files, we'll eventually enhance them here
// For now, just serve them as-is
// For HTML files, enhance them on-the-fly
if strings.HasSuffix(name, ".html") {
fmt.Printf("📄 Serving HTML: %s\n", name)
fmt.Println("🔍 Parser ran!")
// TODO: Parse for insertr elements and enhance
fmt.Printf("📄 Enhancing HTML: %s\n", name)
return efs.serveEnhancedHTML(name)
}
return file, nil
// 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)
}