Fix demo site auto-enhancement and content persistence
- Restructure demo directory from test-sites/ to demos/ with flattened layout - Add auto-enhancement on server startup for all sites with auto_enhance: true - Fix inconsistent content ID generation that prevented dan-eden-portfolio content persistence - Update server configuration to enhance from source to separate output directories - Remove manual enhancement from justfile in favor of automatic server enhancement - Clean up legacy test files and unused restore command - Update build system to use CDN endpoint instead of file copying
@@ -102,7 +102,7 @@ When running `insertr serve`, the server automatically:
|
||||
- **Registers sites** from `insertr.yaml` configuration
|
||||
- **Enhances static files** with latest database content
|
||||
- **Auto-updates files** when content changes via API
|
||||
- **Creates backups** of original files (if enabled)
|
||||
|
||||
|
||||
**Live Enhancement Process:**
|
||||
1. Content updated via API → Database updated
|
||||
|
||||
@@ -458,13 +458,11 @@ server:
|
||||
path: "/var/www/mysite"
|
||||
domain: "mysite.example.com"
|
||||
auto_enhance: true
|
||||
backup_originals: true
|
||||
|
||||
- site_id: "blog"
|
||||
path: "/var/www/blog"
|
||||
domain: "blog.example.com"
|
||||
auto_enhance: true
|
||||
backup_originals: true
|
||||
```
|
||||
|
||||
### **Quick Start**
|
||||
@@ -493,7 +491,6 @@ server:
|
||||
path: "./demo-site"
|
||||
domain: "localhost:3000"
|
||||
auto_enhance: true
|
||||
backup_originals: true
|
||||
|
||||
# API configuration (for remote content API)
|
||||
api:
|
||||
|
||||
2
TODO.md
@@ -140,7 +140,7 @@ internal/
|
||||
### **Production Ready When**:
|
||||
- ✅ Multi-site support with proper site isolation
|
||||
- ✅ Authentication and authorization working
|
||||
- ✅ Database migrations and backup strategy
|
||||
- ✅ Database migrations strategy
|
||||
- ✅ CDN hosting for insertr.js library
|
||||
- ✅ Deployment documentation and examples
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ func runEnhance(cmd *cobra.Command, args []string) {
|
||||
outputDir := viper.GetString("cli.output")
|
||||
|
||||
// Auto-derive site_id for demo paths or validate for production
|
||||
if strings.Contains(inputDir, "/demos/") {
|
||||
if strings.Contains(inputDir, "/demos/") || strings.Contains(inputDir, "./demos/") {
|
||||
// Auto-derive site_id from demo path
|
||||
siteID = content.DeriveOrValidateSiteID(inputDir, siteID)
|
||||
} else {
|
||||
|
||||
163
cmd/restore.go
@@ -1,163 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
|
||||
"github.com/insertr/insertr/internal/content"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
timestamp string
|
||||
latest bool
|
||||
clean bool
|
||||
)
|
||||
|
||||
var restoreCmd = &cobra.Command{
|
||||
Use: "restore [site-id]",
|
||||
Short: "Restore a site from backup",
|
||||
Long: `Restore a registered site from a timestamped backup.
|
||||
|
||||
Examples:
|
||||
insertr restore demo # List available backups
|
||||
insertr restore demo --clean # Restore from oldest backup (cleanest)
|
||||
insertr restore demo --latest # Restore from newest backup
|
||||
insertr restore demo --timestamp 20250910-224704 # Restore from specific backup`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: runRestore,
|
||||
}
|
||||
|
||||
func init() {
|
||||
restoreCmd.Flags().StringVarP(×tamp, "timestamp", "t", "", "specific backup timestamp to restore from")
|
||||
restoreCmd.Flags().BoolVar(&latest, "latest", false, "restore from most recent backup")
|
||||
restoreCmd.Flags().BoolVar(&clean, "clean", false, "restore from oldest backup (cleanest state)")
|
||||
|
||||
// Bind flags to viper
|
||||
viper.BindPFlag("restore.timestamp", restoreCmd.Flags().Lookup("timestamp"))
|
||||
viper.BindPFlag("restore.latest", restoreCmd.Flags().Lookup("latest"))
|
||||
viper.BindPFlag("restore.clean", restoreCmd.Flags().Lookup("clean"))
|
||||
}
|
||||
|
||||
func runRestore(cmd *cobra.Command, args []string) {
|
||||
siteID := args[0]
|
||||
|
||||
// Initialize content client (we don't actually need it for restore, but SiteManager expects it)
|
||||
contentClient := content.NewMockClient()
|
||||
|
||||
// Initialize site manager
|
||||
siteManager := content.NewSiteManager(contentClient, "./insertr-backups", false)
|
||||
|
||||
// Load sites from configuration to register them
|
||||
if siteConfigs := viper.Get("server.sites"); siteConfigs != nil {
|
||||
if configs, ok := siteConfigs.([]interface{}); ok {
|
||||
var sites []*content.SiteConfig
|
||||
for _, configInterface := range configs {
|
||||
if configMap, ok := configInterface.(map[string]interface{}); ok {
|
||||
site := &content.SiteConfig{}
|
||||
if id, ok := configMap["site_id"].(string); ok {
|
||||
site.SiteID = id
|
||||
}
|
||||
if path, ok := configMap["path"].(string); ok {
|
||||
site.Path = path
|
||||
}
|
||||
if domain, ok := configMap["domain"].(string); ok {
|
||||
site.Domain = domain
|
||||
}
|
||||
if autoEnhance, ok := configMap["auto_enhance"].(bool); ok {
|
||||
site.AutoEnhance = autoEnhance
|
||||
}
|
||||
if backupOriginals, ok := configMap["backup_originals"].(bool); ok {
|
||||
site.BackupOriginals = backupOriginals
|
||||
}
|
||||
sites = append(sites, site)
|
||||
}
|
||||
}
|
||||
|
||||
if err := siteManager.RegisterSites(sites); err != nil {
|
||||
log.Fatalf("Failed to register sites: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// List available backups
|
||||
backups, err := siteManager.ListBackups(siteID)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to list backups: %v", err)
|
||||
}
|
||||
|
||||
if len(backups) == 0 {
|
||||
fmt.Printf("❌ No backups found for site '%s'\n", siteID)
|
||||
fmt.Printf("💡 Backups are created automatically during enhancement when backup_originals is enabled\n")
|
||||
return
|
||||
}
|
||||
|
||||
// Sort backups chronologically
|
||||
sort.Strings(backups)
|
||||
|
||||
// Handle different restore modes
|
||||
var targetTimestamp string
|
||||
|
||||
if timestamp != "" {
|
||||
// Specific timestamp provided
|
||||
targetTimestamp = timestamp
|
||||
found := false
|
||||
for _, backup := range backups {
|
||||
if backup == targetTimestamp {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
fmt.Printf("❌ Backup timestamp '%s' not found for site '%s'\n", targetTimestamp, siteID)
|
||||
fmt.Printf("📋 Available backups:\n")
|
||||
for i, backup := range backups {
|
||||
if i == 0 {
|
||||
fmt.Printf(" %s (oldest/cleanest)\n", backup)
|
||||
} else if i == len(backups)-1 {
|
||||
fmt.Printf(" %s (newest)\n", backup)
|
||||
} else {
|
||||
fmt.Printf(" %s\n", backup)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
} else if clean {
|
||||
// Restore from oldest backup (cleanest)
|
||||
targetTimestamp = backups[0]
|
||||
fmt.Printf("🧹 Restoring from oldest backup (cleanest state): %s\n", targetTimestamp)
|
||||
} else if latest {
|
||||
// Restore from newest backup
|
||||
targetTimestamp = backups[len(backups)-1]
|
||||
fmt.Printf("🔄 Restoring from newest backup: %s\n", targetTimestamp)
|
||||
} else {
|
||||
// No specific option - list available backups
|
||||
fmt.Printf("📋 Available backups for site '%s':\n", siteID)
|
||||
for i, backup := range backups {
|
||||
if i == 0 {
|
||||
fmt.Printf(" %s (oldest/cleanest) ← use --clean\n", backup)
|
||||
} else if i == len(backups)-1 {
|
||||
fmt.Printf(" %s (newest) ← use --latest\n", backup)
|
||||
} else {
|
||||
fmt.Printf(" %s\n", backup)
|
||||
}
|
||||
}
|
||||
fmt.Printf("\nUsage:\n")
|
||||
fmt.Printf(" insertr restore %s --clean # restore from oldest backup\n", siteID)
|
||||
fmt.Printf(" insertr restore %s --latest # restore from newest backup\n", siteID)
|
||||
fmt.Printf(" insertr restore %s --timestamp %s # restore from specific backup\n", siteID, backups[0])
|
||||
return
|
||||
}
|
||||
|
||||
// Perform restore
|
||||
fmt.Printf("🔄 Restoring site '%s' from backup %s...\n", siteID, targetTimestamp)
|
||||
|
||||
if err := siteManager.RestoreFromBackup(siteID, targetTimestamp); err != nil {
|
||||
log.Fatalf("❌ Restore failed: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("✅ Successfully restored site '%s' from backup %s\n", siteID, targetTimestamp)
|
||||
fmt.Printf("💡 Site files have been restored to their state from %s\n", targetTimestamp)
|
||||
}
|
||||
@@ -52,7 +52,6 @@ func init() {
|
||||
|
||||
rootCmd.AddCommand(enhanceCmd)
|
||||
rootCmd.AddCommand(serveCmd)
|
||||
rootCmd.AddCommand(restoreCmd)
|
||||
}
|
||||
|
||||
func initConfig() {
|
||||
|
||||
16
cmd/serve.go
@@ -73,7 +73,7 @@ func runServe(cmd *cobra.Command, args []string) {
|
||||
contentClient := content.NewDatabaseClient(database)
|
||||
|
||||
// Initialize site manager
|
||||
siteManager := content.NewSiteManager(contentClient, "./insertr-backups", devMode)
|
||||
siteManager := content.NewSiteManager(contentClient, devMode)
|
||||
|
||||
// Load sites from configuration
|
||||
if siteConfigs := viper.Get("server.sites"); siteConfigs != nil {
|
||||
@@ -88,15 +88,15 @@ func runServe(cmd *cobra.Command, args []string) {
|
||||
if path, ok := configMap["path"].(string); ok {
|
||||
site.Path = path
|
||||
}
|
||||
if sourcePath, ok := configMap["source_path"].(string); ok {
|
||||
site.SourcePath = sourcePath
|
||||
}
|
||||
if domain, ok := configMap["domain"].(string); ok {
|
||||
site.Domain = domain
|
||||
}
|
||||
if autoEnhance, ok := configMap["auto_enhance"].(bool); ok {
|
||||
site.AutoEnhance = autoEnhance
|
||||
}
|
||||
if backupOriginals, ok := configMap["backup_originals"].(bool); ok {
|
||||
site.BackupOriginals = backupOriginals
|
||||
}
|
||||
if site.SiteID != "" && site.Path != "" {
|
||||
sites = append(sites, site)
|
||||
}
|
||||
@@ -108,6 +108,14 @@ func runServe(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-enhance sites if enabled
|
||||
if devMode {
|
||||
log.Printf("🔄 Auto-enhancing sites in development mode...")
|
||||
if err := siteManager.EnhanceAllSites(); err != nil {
|
||||
log.Printf("⚠️ Some sites failed to enhance: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize handlers
|
||||
contentHandler := api.NewContentHandler(database, authService)
|
||||
contentHandler.SetSiteManager(siteManager)
|
||||
|
||||
@@ -8,7 +8,7 @@ Successfully established a comprehensive testing infrastructure for insertr CMS
|
||||
|
||||
### ✅ Directory Structure
|
||||
```
|
||||
test-sites/
|
||||
demos/
|
||||
├── simple/ # Simple vanilla CSS sites
|
||||
│ └── dan-eden-portfolio/ # ✅ COMPLETE
|
||||
├── framework-based/ # CSS framework sites
|
||||
@@ -59,7 +59,7 @@ test-sites/
|
||||
|
||||
### ✅ Developer Experience
|
||||
- Simple enhancement workflow: download → add classes → enhance → serve
|
||||
- Automatic backup of originals
|
||||
|
||||
- Clear feedback on enhancement results
|
||||
|
||||
## Comparison with Demo Site
|
||||
@@ -126,7 +126,7 @@ just demo dan-eden
|
||||
just list-demos
|
||||
|
||||
# Test demo infrastructure
|
||||
node test-sites/scripts/test-demo.js
|
||||
node demos/scripts/test-demo.js
|
||||
```
|
||||
|
||||
### **Demo Sites Available**
|
||||
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
@@ -10,7 +10,7 @@ database:
|
||||
|
||||
# Demo-specific configuration
|
||||
demo:
|
||||
site_id: "dan-eden" # Unique site ID for Dan Eden demo
|
||||
site_id: "dan-eden-portfolio" # Unique site ID for Dan Eden demo
|
||||
inject_demo_gate: true # Auto-inject demo gate if no gates exist
|
||||
mock_auth: true # Use mock authentication for demos
|
||||
api_endpoint: "http://localhost:8080/api/content"
|
||||
@@ -18,8 +18,8 @@ demo:
|
||||
|
||||
# CLI enhancement configuration
|
||||
cli:
|
||||
site_id: "dan-eden" # Site ID for this demo
|
||||
output: "./dan-eden-demo" # Output directory for enhanced files
|
||||
site_id: "dan-eden-portfolio" # Site ID for this demo
|
||||
output: "./demos/dan-eden-portfolio_enhanced" # Output directory for enhanced files
|
||||
inject_demo_gate: true # Inject demo gate in development mode
|
||||
|
||||
# Authentication configuration (for demo)
|
||||
@@ -4,16 +4,16 @@
|
||||
<title>About - Acme Consulting Services</title>
|
||||
<link rel="stylesheet" href="assets/style.css"/>
|
||||
|
||||
</head>
|
||||
<script src="http://localhost:8080/insertr.js" data-insertr-injected="true" data-site-id="demo" data-api-endpoint="http://localhost:8080/api/content" data-mock-auth="true" data-debug="true"></script></head>
|
||||
<body>
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar">
|
||||
<div class="container">
|
||||
<h1 class="logo insertr" data-content-id="about-logo-bf9558" data-content-type="text">Acme Consulting</h1>
|
||||
<ul class="nav-links">
|
||||
<li><a href="index.html">Home</a></li>
|
||||
<li><a href="about.html">About</a></li>
|
||||
<li><a href="contact.html">Contact</a></li>
|
||||
<li class="insertr" data-content-id="about-li-0babbf" data-content-type="text"><a href="index.html">Home</a></li>
|
||||
<li class="insertr" data-content-id="about-li-2-0babbf" data-content-type="text"><a href="about.html">About</a></li>
|
||||
<li class="insertr" data-content-id="about-li-3-0babbf" data-content-type="text"><a href="contact.html">Contact</a></li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
@@ -32,11 +32,11 @@
|
||||
<div class="container">
|
||||
<h2 class="insertr" data-content-id="about-h2-246854" data-content-type="text">Our Story</h2>
|
||||
<div class="insertr-group">
|
||||
<p>Founded in 2020, Acme Consulting emerged from a simple observation: small businesses needed access to the same high-quality strategic advice that large corporations receive, but in a format that was accessible, affordable, and actionable.</p>
|
||||
<p class="insertr" data-content-id="about-p-b2f44a" data-content-type="markdown">Founded in 2020, Acme Consulting emerged from a simple observation: small businesses needed access to the same high-quality strategic advice that large corporations receive, but in a format that was accessible, affordable, and actionable.</p>
|
||||
|
||||
<p>Our founders, with combined experience of over 30 years in business strategy, operations, and technology, recognized that the traditional consulting model wasn't serving the needs of growing businesses. We set out to change that.</p>
|
||||
<p class="insertr" data-content-id="about-p-2-b2f44a" data-content-type="markdown">Our founders, with combined experience of over 30 years in business strategy, operations, and technology, recognized that the traditional consulting model wasn't serving the needs of growing businesses. We set out to change that.</p>
|
||||
|
||||
<p>Today, we've helped over **200 businesses** streamline their operations, clarify their strategy, and achieve sustainable growth. Our approach combines proven methodologies with a deep understanding of the unique challenges facing small to medium-sized businesses.</p>
|
||||
<p class="insertr" data-content-id="about-p-3-b2f44a" data-content-type="markdown">Today, we've helped over **200 businesses** streamline their operations, clarify their strategy, and achieve sustainable growth. Our approach combines proven methodologies with a deep understanding of the unique challenges facing small to medium-sized businesses.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -49,21 +49,21 @@
|
||||
|
||||
<div class="services-grid" style="margin-top: 3rem;">
|
||||
<div class="service-card">
|
||||
<div class="insertr">
|
||||
<div class="insertr" data-content-id="about-div-dac2cd" data-content-type="markdown">
|
||||
<h3>Sarah Chen</h3>
|
||||
<p><strong>Founder & CEO</strong></p>
|
||||
<p>Former <strong>McKinsey consultant</strong> with 15 years of experience in strategy and operations. MBA from Stanford.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="service-card">
|
||||
<div class="insertr">
|
||||
<div class="insertr" data-content-id="about-div-2-dac2cd" data-content-type="markdown">
|
||||
<h3>Michael Rodriguez</h3>
|
||||
<p><strong>Head of Operations</strong></p>
|
||||
<p>20 years in manufacturing and supply chain optimization. Expert in <strong>lean methodologies</strong> and process improvement.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="service-card">
|
||||
<div class="insertr">
|
||||
<div class="insertr" data-content-id="about-div-3-dac2cd" data-content-type="markdown">
|
||||
<h3>Emma Thompson</h3>
|
||||
<p><strong>Digital Strategy Lead</strong></p>
|
||||
<p>Former tech startup founder turned consultant. Specializes in <em>digital transformation</em> and technology adoption.</p>
|
||||
@@ -101,8 +101,8 @@
|
||||
|
||||
<!-- Test 1: .insertr container expansion (should make each p individually editable) -->
|
||||
<div style="margin-bottom: 2rem;">
|
||||
<h3>Test 1: Container Expansion (.insertr)</h3>
|
||||
<div class="insertr" style="border: 2px dashed #ccc; padding: 1rem;">
|
||||
<h3 class="insertr" data-content-id="about-h3-ea6b29" data-content-type="text">Test 1: Container Expansion (.insertr)</h3>
|
||||
<div class="insertr" style="border: 2px dashed #ccc; padding: 1rem;" data-content-id="about-div-4-e2aa93" data-content-type="markdown">
|
||||
<p>This paragraph should be individually editable with a textarea.</p>
|
||||
<p>This second paragraph should also be individually editable.</p>
|
||||
<p>Each paragraph should get its own modal when clicked.</p>
|
||||
@@ -111,11 +111,11 @@
|
||||
|
||||
<!-- Test 2: .insertr-group collective editing (should edit all together) -->
|
||||
<div>
|
||||
<h3>Test 2: Group Editing (.insertr-group)</h3>
|
||||
<h3 class="insertr" data-content-id="about-h3-2-ea6b29" data-content-type="text">Test 2: Group Editing (.insertr-group)</h3>
|
||||
<div class="insertr-group" style="border: 2px solid #007cba; padding: 1rem;">
|
||||
<p>This paragraph is part of a <strong>group</strong>.</p>
|
||||
<p>Clicking anywhere should open one markdown editor with <em>rich formatting</em>.</p>
|
||||
<p>All content should be <strong>editable together</strong> as markdown with proper <em>HTML conversion</em>.</p>
|
||||
<p class="insertr" data-content-id="about-p-4-dcfaf1" data-content-type="markdown">This paragraph is part of a <strong>group</strong>.</p>
|
||||
<p class="insertr" data-content-id="about-p-5-dcfaf1" data-content-type="markdown">Clicking anywhere should open one markdown editor with <em>rich formatting</em>.</p>
|
||||
<p class="insertr" data-content-id="about-p-6-dcfaf1" data-content-type="markdown">All content should be <strong>editable together</strong> as markdown with proper <em>HTML conversion</em>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -4,16 +4,15 @@
|
||||
<title>Acme Consulting Services - Live Reload Test</title>
|
||||
<link rel="stylesheet" href="assets/style.css"/>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar">
|
||||
<div class="container">
|
||||
<h1 class="logo insertr" data-content-id="index-logo-c176ba" data-content-type="text">Acme Consulting</h1>
|
||||
<ul class="nav-links">
|
||||
<li><a href="index.html">Home</a></li>
|
||||
<li><a href="about.html">About</a></li>
|
||||
<li><a href="contact.html">Contact</a></li>
|
||||
<li class="insertr" data-content-id="index-li-bf7136" data-content-type="text"><a href="index.html">Home</a></li>
|
||||
<li class="insertr" data-content-id="index-li-2-bf7136" data-content-type="text"><a href="about.html">About</a></li>
|
||||
<li class="insertr" data-content-id="index-li-3-bf7136" data-content-type="text"><a href="contact.html">Contact</a></li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
@@ -78,8 +77,7 @@
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Insertr JavaScript Library -->
|
||||
<script type="text/javascript" src="insertr.js"></script>
|
||||
|
||||
|
||||
|
||||
</body></html>
|
||||
@@ -10,7 +10,7 @@ database:
|
||||
|
||||
# Demo-specific configuration
|
||||
demo:
|
||||
site_id: "demo" # Unique site ID for default demo
|
||||
site_id: "default" # Unique site ID for default demo
|
||||
inject_demo_gate: true # Auto-inject demo gate if no gates exist
|
||||
mock_auth: true # Use mock authentication for demos
|
||||
api_endpoint: "http://localhost:8080/api/content"
|
||||
@@ -18,8 +18,8 @@ demo:
|
||||
|
||||
# CLI enhancement configuration
|
||||
cli:
|
||||
site_id: "demo" # Site ID for this demo
|
||||
output: "./demo_enhanced" # Output directory for enhanced files
|
||||
site_id: "default" # Site ID for this demo
|
||||
output: "./demos/default_enhanced" # Output directory for enhanced files
|
||||
inject_demo_gate: true # Inject demo gate in development mode
|
||||
|
||||
# Authentication configuration (for demo)
|
||||
@@ -13,7 +13,7 @@ console.log('=====================================\n');
|
||||
// Test 1: Check if enhanced sites exist
|
||||
console.log('📁 Checking enhanced test sites...');
|
||||
|
||||
const danEdenPath = './test-sites/simple/dan-eden-portfolio-enhanced';
|
||||
const danEdenPath = './demos/simple/dan-eden-portfolio-enhanced';
|
||||
if (fs.existsSync(danEdenPath)) {
|
||||
console.log('✅ Dan Eden enhanced site exists');
|
||||
|
||||
@@ -32,7 +32,7 @@ if (fs.existsSync(danEdenPath)) {
|
||||
}
|
||||
} else {
|
||||
console.log('❌ Dan Eden enhanced site not found');
|
||||
console.log(' Run: just enhance-test-sites');
|
||||
console.log(' Run: just enhance-demos');
|
||||
}
|
||||
|
||||
console.log('\n🎯 Demo Commands Available:');
|
||||
19
demos/simple/index.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html><html><head>
|
||||
<title>Simple Test</title>
|
||||
<script src="http://localhost:8080/insertr.js" data-insertr-injected="true" data-site-id="simple" data-api-endpoint="http://localhost:8080/api/content" data-mock-auth="true" data-debug="true"></script></head>
|
||||
<body>
|
||||
<h1 class="insertr" data-content-id="index-h1-e0f926" data-content-type="text">Welcome, you!!</h1>
|
||||
<p class="insertr" data-content-id="index-p-b376ed" data-content-type="markdown">This is a <strong>test</strong> paragraph with <a href="/">a link</a>.</p>
|
||||
<div>
|
||||
<h2 class="insertr" data-content-id="index-h2-d8622b" data-content-type="text">Section Title</h2>
|
||||
<p class="insertr" data-content-id="index-p-2-daa8f5" data-content-type="markdown">Another paragraph here.</p>
|
||||
<button class="insertr" data-content-id="index-button-41ef19" data-content-type="link">Click Me</button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="insertr-demo-gate" style="position: fixed; top: 20px; right: 20px; z-index: 9999; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
|
||||
<button class="insertr-gate insertr-demo-gate-btn insertr" style="background: #4f46e5; color: white; border: none; padding: 10px 16px; border-radius: 8px; font-size: 14px; font-weight: 500; cursor: pointer; box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3); transition: all 0.2s ease; display: flex; align-items: center; gap: 8px; user-select: none;" onmouseover="this.style.background='#4338ca'; this.style.transform='translateY(-2px)'; this.style.boxShadow='0 6px 16px rgba(79, 70, 229, 0.4)'" onmouseout="this.style.background='#4f46e5'; this.style.transform='translateY(0)'; this.style.boxShadow='0 4px 12px rgba(79, 70, 229, 0.3)'" data-content-id="index-insertr-gate-61c9aa" data-content-type="link">
|
||||
<span style="font-size: 16px;">✏️</span>
|
||||
<span>Edit Site</span>
|
||||
</button>
|
||||
</div></body></html>
|
||||
@@ -19,7 +19,7 @@ demo:
|
||||
# CLI enhancement configuration
|
||||
cli:
|
||||
site_id: "simple" # Site ID for this demo
|
||||
output: "./simple_enhanced" # Output directory for enhanced files
|
||||
output: "./demos/simple_enhanced" # Output directory for enhanced files
|
||||
inject_demo_gate: true # Inject demo gate in development mode
|
||||
|
||||
# Authentication configuration (for demo)
|
||||
20
insertr.yaml
@@ -12,31 +12,31 @@ database:
|
||||
server:
|
||||
port: 8080 # HTTP API server port
|
||||
sites: # Registered sites for file-based enhancement
|
||||
- site_id: "demo"
|
||||
path: "./test-sites/demo-site_enhanced"
|
||||
- site_id: "default"
|
||||
path: "./demos/default_enhanced"
|
||||
source_path: "./demos/default"
|
||||
domain: "localhost:3000"
|
||||
auto_enhance: true
|
||||
backup_originals: true
|
||||
- site_id: "simple"
|
||||
path: "./test-sites/simple/test-simple_enhanced"
|
||||
path: "./demos/simple_enhanced"
|
||||
source_path: "./demos/simple"
|
||||
domain: "localhost:3000"
|
||||
auto_enhance: true
|
||||
backup_originals: true
|
||||
- site_id: "dan-eden"
|
||||
path: "./test-sites/simple/dan-eden-portfolio_enhanced"
|
||||
- site_id: "dan-eden-portfolio"
|
||||
path: "./demos/dan-eden-portfolio_enhanced"
|
||||
source_path: "./demos/dan-eden-portfolio"
|
||||
domain: "localhost:3000"
|
||||
auto_enhance: true
|
||||
backup_originals: true
|
||||
# Example additional site configuration:
|
||||
# - site_id: "mysite"
|
||||
# path: "/var/www/mysite"
|
||||
# domain: "mysite.example.com"
|
||||
# auto_enhance: true
|
||||
# backup_originals: true
|
||||
|
||||
|
||||
# CLI enhancement configuration
|
||||
cli:
|
||||
site_id: "demo" # Default site ID for CLI operations
|
||||
site_id: "default" # Default site ID for CLI operations
|
||||
output: "./dist" # Default output directory for enhanced files
|
||||
inject_demo_gate: true # Inject demo gate in development mode if no gates exist
|
||||
|
||||
|
||||
@@ -192,6 +192,11 @@ func (e *Enhancer) enhanceWithEngine(htmlContent []byte, filePath string) ([]byt
|
||||
return []byte(buf.String()), nil
|
||||
}
|
||||
|
||||
// SetSiteID sets the site ID for the enhancer
|
||||
func (e *Enhancer) SetSiteID(siteID string) {
|
||||
e.siteID = siteID
|
||||
}
|
||||
|
||||
// EnhanceInPlace performs in-place enhancement of static site files
|
||||
func (e *Enhancer) EnhanceInPlace(sitePath string, siteID string) error {
|
||||
// Use the provided siteID (derivation should happen at CLI level)
|
||||
@@ -204,7 +209,7 @@ func (e *Enhancer) EnhanceInPlace(sitePath string, siteID string) error {
|
||||
// DeriveOrValidateSiteID automatically derives site_id for demo paths or validates for production
|
||||
func DeriveOrValidateSiteID(sitePath string, configSiteID string) string {
|
||||
// Check if this is a demo path
|
||||
if strings.Contains(sitePath, "/demos/") {
|
||||
if strings.Contains(sitePath, "/demos/") || strings.Contains(sitePath, "./demos/") {
|
||||
return deriveDemoSiteID(sitePath)
|
||||
}
|
||||
|
||||
@@ -222,10 +227,10 @@ func deriveDemoSiteID(sitePath string) string {
|
||||
}
|
||||
absPath = filepath.Clean(absPath)
|
||||
|
||||
// Look for patterns in demo paths:
|
||||
// demos/demo-site_enhanced -> "demo"
|
||||
// demos/simple/test-simple_enhanced -> "simple"
|
||||
// demos/simple/dan-eden-portfolio -> "dan-eden"
|
||||
// Flattened structure - just use directory name after demos/
|
||||
// demos/default -> "default"
|
||||
// demos/simple -> "simple"
|
||||
// demos/dan-eden-portfolio -> "dan-eden-portfolio"
|
||||
|
||||
parts := strings.Split(absPath, string(filepath.Separator))
|
||||
|
||||
@@ -240,41 +245,14 @@ func deriveDemoSiteID(sitePath string) string {
|
||||
|
||||
if demosIndex == -1 || demosIndex >= len(parts)-1 {
|
||||
// Fallback if demos not found in path
|
||||
return "demo"
|
||||
return "default"
|
||||
}
|
||||
|
||||
// Get the segment after demos/
|
||||
nextSegment := parts[demosIndex+1]
|
||||
// Get the segment after demos/ and clean it
|
||||
dirName := parts[demosIndex+1]
|
||||
dirName = strings.TrimSuffix(dirName, "_enhanced")
|
||||
|
||||
// Handle different demo directory patterns:
|
||||
if strings.HasPrefix(nextSegment, "demo-site") {
|
||||
return "demo"
|
||||
}
|
||||
|
||||
if nextSegment == "simple" && len(parts) > demosIndex+2 {
|
||||
// For demos/simple/something, use the something part
|
||||
finalSegment := parts[demosIndex+2]
|
||||
|
||||
// Extract meaningful name from directory
|
||||
if strings.HasPrefix(finalSegment, "test-") {
|
||||
// test-simple_enhanced -> simple
|
||||
return "simple"
|
||||
}
|
||||
if strings.Contains(finalSegment, "dan-eden") {
|
||||
return "dan-eden"
|
||||
}
|
||||
|
||||
// Generic case: use the directory name as-is
|
||||
return strings.TrimSuffix(finalSegment, "_enhanced")
|
||||
}
|
||||
|
||||
// Default case: use the immediate subdirectory of demos/
|
||||
result := strings.TrimSuffix(nextSegment, "_enhanced")
|
||||
|
||||
// Clean up common suffixes
|
||||
result = strings.TrimSuffix(result, "-site")
|
||||
|
||||
return result
|
||||
return dirName
|
||||
}
|
||||
|
||||
// copyFile copies a file from src to dst
|
||||
|
||||
@@ -5,41 +5,35 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/insertr/insertr/internal/engine"
|
||||
)
|
||||
|
||||
// SiteConfig represents configuration for a registered site
|
||||
type SiteConfig struct {
|
||||
SiteID string `yaml:"site_id"`
|
||||
Path string `yaml:"path"`
|
||||
Domain string `yaml:"domain,omitempty"`
|
||||
AutoEnhance bool `yaml:"auto_enhance"`
|
||||
BackupOriginals bool `yaml:"backup_originals"`
|
||||
SiteID string `yaml:"site_id"`
|
||||
Path string `yaml:"path"` // Served path (enhanced output)
|
||||
SourcePath string `yaml:"source_path"` // Source path (for enhancement)
|
||||
Domain string `yaml:"domain,omitempty"`
|
||||
AutoEnhance bool `yaml:"auto_enhance"`
|
||||
}
|
||||
|
||||
// SiteManager handles registration and enhancement of static sites
|
||||
type SiteManager struct {
|
||||
sites map[string]*SiteConfig
|
||||
enhancer *Enhancer
|
||||
mutex sync.RWMutex
|
||||
backupDir string
|
||||
devMode bool
|
||||
sites map[string]*SiteConfig
|
||||
enhancer *Enhancer
|
||||
mutex sync.RWMutex
|
||||
devMode bool
|
||||
}
|
||||
|
||||
// NewSiteManager creates a new site manager
|
||||
func NewSiteManager(contentClient engine.ContentClient, backupDir string, devMode bool) *SiteManager {
|
||||
if backupDir == "" {
|
||||
backupDir = "./insertr-backups"
|
||||
}
|
||||
|
||||
func NewSiteManager(contentClient engine.ContentClient, devMode bool) *SiteManager {
|
||||
return &SiteManager{
|
||||
sites: make(map[string]*SiteConfig),
|
||||
enhancer: NewDefaultEnhancer(contentClient, ""), // siteID will be set per operation
|
||||
backupDir: backupDir,
|
||||
devMode: devMode,
|
||||
sites: make(map[string]*SiteConfig),
|
||||
enhancer: NewDefaultEnhancer(contentClient, ""), // siteID will be set per operation
|
||||
devMode: devMode,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,9 +50,17 @@ func (sm *SiteManager) RegisterSite(config *SiteConfig) error {
|
||||
return fmt.Errorf("path is required for site %s", config.SiteID)
|
||||
}
|
||||
|
||||
// Check if path exists
|
||||
// Check if path exists, auto-create enhancement directories
|
||||
if _, err := os.Stat(config.Path); os.IsNotExist(err) {
|
||||
return fmt.Errorf("site path does not exist: %s", config.Path)
|
||||
// Auto-create directory if it appears to be an enhancement target
|
||||
if strings.HasSuffix(config.Path, "_enhanced") {
|
||||
log.Printf("📁 Creating enhancement directory: %s", config.Path)
|
||||
if err := os.MkdirAll(config.Path, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create enhancement directory %s: %w", config.Path, err)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("site path does not exist: %s", config.Path)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert to absolute path
|
||||
@@ -115,7 +117,7 @@ func (sm *SiteManager) IsAutoEnhanceEnabled(siteID string) bool {
|
||||
return exists && site.AutoEnhance
|
||||
}
|
||||
|
||||
// EnhanceSite performs in-place enhancement of a registered site
|
||||
// EnhanceSite performs enhancement from source to output directory
|
||||
func (sm *SiteManager) EnhanceSite(siteID string) error {
|
||||
sm.mutex.RLock()
|
||||
site, exists := sm.sites[siteID]
|
||||
@@ -125,18 +127,25 @@ func (sm *SiteManager) EnhanceSite(siteID string) error {
|
||||
return fmt.Errorf("site %s is not registered", siteID)
|
||||
}
|
||||
|
||||
log.Printf("🔄 Enhancing site %s at %s", siteID, site.Path)
|
||||
// Use source path if available, otherwise use main path (for backwards compatibility)
|
||||
sourcePath := site.SourcePath
|
||||
if sourcePath == "" {
|
||||
sourcePath = site.Path
|
||||
}
|
||||
outputPath := site.Path
|
||||
|
||||
// Create backup if enabled
|
||||
if site.BackupOriginals {
|
||||
if err := sm.createBackup(siteID, site.Path); err != nil {
|
||||
log.Printf("⚠️ Failed to create backup for site %s: %v", siteID, err)
|
||||
// Continue with enhancement even if backup fails
|
||||
}
|
||||
log.Printf("🔄 Enhancing site %s from %s to %s", siteID, sourcePath, outputPath)
|
||||
|
||||
// Create output directory if it doesn't exist
|
||||
if err := os.MkdirAll(outputPath, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create output directory %s: %w", outputPath, err)
|
||||
}
|
||||
|
||||
// Perform in-place enhancement
|
||||
if err := sm.enhancer.EnhanceInPlace(site.Path, siteID); err != nil {
|
||||
// Set site ID on enhancer
|
||||
sm.enhancer.SetSiteID(siteID)
|
||||
|
||||
// Perform enhancement from source to output
|
||||
if err := sm.enhancer.EnhanceDirectory(sourcePath, outputPath); err != nil {
|
||||
return fmt.Errorf("failed to enhance site %s: %w", siteID, err)
|
||||
}
|
||||
|
||||
@@ -169,121 +178,6 @@ func (sm *SiteManager) EnhanceAllSites() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// createBackup creates a timestamped backup of the site
|
||||
func (sm *SiteManager) createBackup(siteID, sitePath string) error {
|
||||
// Create backup directory structure
|
||||
timestamp := time.Now().Format("20060102-150405")
|
||||
backupPath := filepath.Join(sm.backupDir, siteID, timestamp)
|
||||
|
||||
if err := os.MkdirAll(backupPath, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create backup directory: %w", err)
|
||||
}
|
||||
|
||||
// Copy HTML files to backup
|
||||
return filepath.Walk(sitePath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Only backup HTML files
|
||||
if !info.IsDir() && filepath.Ext(path) == ".html" {
|
||||
relPath, err := filepath.Rel(sitePath, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
backupFilePath := filepath.Join(backupPath, relPath)
|
||||
|
||||
// Create directory structure in backup
|
||||
if err := os.MkdirAll(filepath.Dir(backupFilePath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy file
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(backupFilePath, content, info.Mode())
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// RestoreFromBackup restores a site from a specific backup
|
||||
func (sm *SiteManager) RestoreFromBackup(siteID, timestamp string) error {
|
||||
sm.mutex.RLock()
|
||||
site, exists := sm.sites[siteID]
|
||||
sm.mutex.RUnlock()
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("site %s is not registered", siteID)
|
||||
}
|
||||
|
||||
backupPath := filepath.Join(sm.backupDir, siteID, timestamp)
|
||||
if _, err := os.Stat(backupPath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("backup not found: %s", backupPath)
|
||||
}
|
||||
|
||||
log.Printf("🔄 Restoring site %s from backup %s", siteID, timestamp)
|
||||
|
||||
// Copy backup files back to site directory
|
||||
return filepath.Walk(backupPath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
relPath, err := filepath.Rel(backupPath, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targetPath := filepath.Join(site.Path, relPath)
|
||||
|
||||
// Create directory structure
|
||||
if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy file
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(targetPath, content, info.Mode())
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ListBackups returns available backups for a site
|
||||
func (sm *SiteManager) ListBackups(siteID string) ([]string, error) {
|
||||
backupSitePath := filepath.Join(sm.backupDir, siteID)
|
||||
|
||||
if _, err := os.Stat(backupSitePath); os.IsNotExist(err) {
|
||||
return []string{}, nil // No backups exist
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(backupSitePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read backup directory: %w", err)
|
||||
}
|
||||
|
||||
var backups []string
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
backups = append(backups, entry.Name())
|
||||
}
|
||||
}
|
||||
|
||||
return backups, nil
|
||||
}
|
||||
|
||||
// GetStats returns statistics about registered sites
|
||||
func (sm *SiteManager) GetStats() map[string]interface{} {
|
||||
sm.mutex.RLock()
|
||||
@@ -299,6 +193,5 @@ func (sm *SiteManager) GetStats() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"total_sites": len(sm.sites),
|
||||
"auto_enhance_sites": autoEnhanceCount,
|
||||
"backup_directory": sm.backupDir,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,12 +33,8 @@ func (g *IDGenerator) Generate(node *html.Node, filePath string) string {
|
||||
tag := strings.ToLower(node.Data)
|
||||
primaryClass := g.getPrimaryClass(node)
|
||||
|
||||
// 3. Position context (simple)
|
||||
elementKey := g.getElementKey(fileName, tag, primaryClass)
|
||||
index := g.getElementIndex(elementKey)
|
||||
|
||||
// 4. Build readable prefix
|
||||
prefix := g.buildPrefix(fileName, tag, primaryClass, index)
|
||||
// 3. Build readable prefix (deterministic, no runtime counting)
|
||||
prefix := g.buildDeterministicPrefix(fileName, tag, primaryClass)
|
||||
|
||||
// 5. Add collision-resistant suffix
|
||||
signature := g.createSignature(node, filePath)
|
||||
@@ -84,7 +80,22 @@ func (g *IDGenerator) getElementIndex(elementKey string) int {
|
||||
return g.elementCounts[elementKey]
|
||||
}
|
||||
|
||||
// buildPrefix creates human-readable prefix for the ID
|
||||
// buildDeterministicPrefix creates human-readable prefix without runtime counting
|
||||
func (g *IDGenerator) buildDeterministicPrefix(fileName, tag, primaryClass string) string {
|
||||
var parts []string
|
||||
parts = append(parts, fileName)
|
||||
|
||||
if primaryClass != "" {
|
||||
parts = append(parts, primaryClass)
|
||||
} else {
|
||||
parts = append(parts, tag)
|
||||
}
|
||||
|
||||
// No runtime index - rely on hash for uniqueness
|
||||
return strings.Join(parts, "-")
|
||||
}
|
||||
|
||||
// buildPrefix creates human-readable prefix for the ID (legacy method)
|
||||
func (g *IDGenerator) buildPrefix(fileName, tag, primaryClass string, index int) string {
|
||||
var parts []string
|
||||
parts = append(parts, fileName)
|
||||
|
||||
78
justfile
@@ -17,12 +17,7 @@ dev: build-lib build
|
||||
echo "================================================"
|
||||
echo ""
|
||||
|
||||
# Enhance demo site if needed
|
||||
if [ ! -d "./test-sites/demo-site_enhanced" ]; then
|
||||
echo "🔧 Demo site not ready - enhancing now..."
|
||||
./insertr enhance test-sites/demo-site --output test-sites/demo-site_enhanced --config test-sites/demo-site/insertr.yaml
|
||||
echo "✅ Demo site enhanced!"
|
||||
fi
|
||||
# Note: Sites are auto-enhanced by the server on startup
|
||||
|
||||
echo ""
|
||||
echo "🔌 Starting Insertr server with all sites..."
|
||||
@@ -45,9 +40,9 @@ dev: build-lib build
|
||||
|
||||
echo ""
|
||||
echo "🌐 All sites available at:"
|
||||
echo " Demo site: http://localhost:8080/sites/demo/"
|
||||
echo " Default site: http://localhost:8080/sites/default/"
|
||||
echo " Simple site: http://localhost:8080/sites/simple/"
|
||||
echo " Dan Eden site: http://localhost:8080/sites/dan-eden/"
|
||||
echo " Dan Eden site: http://localhost:8080/sites/dan-eden-portfolio/"
|
||||
echo ""
|
||||
echo "📝 Full-stack ready - edit content with real-time persistence!"
|
||||
echo "🔄 Press Ctrl+C to shutdown"
|
||||
@@ -60,7 +55,7 @@ dev: build-lib build
|
||||
dev-about: build-lib build
|
||||
#!/usr/bin/env bash
|
||||
echo "🚀 Starting full-stack development..."
|
||||
echo "🌐 About page available at: http://localhost:8080/sites/demo/about.html"
|
||||
echo "🌐 About page available at: http://localhost:8080/sites/default/about.html"
|
||||
INSERTR_DATABASE_PATH=./insertr.db ./insertr serve --dev-mode
|
||||
|
||||
# Check project status and validate setup
|
||||
@@ -70,9 +65,9 @@ check:
|
||||
# Simple demo launcher - all sites now served from main server
|
||||
demo:
|
||||
@echo "🌐 All demo sites are served from the main server:"
|
||||
@echo " http://localhost:8080/sites/demo/ - Main demo site"
|
||||
@echo " http://localhost:8080/sites/default/ - Main demo site"
|
||||
@echo " http://localhost:8080/sites/simple/ - Simple test site"
|
||||
@echo " http://localhost:8080/sites/dan-eden/ - Dan Eden portfolio"
|
||||
@echo " http://localhost:8080/sites/dan-eden-portfolio/ - Dan Eden portfolio"
|
||||
@echo ""
|
||||
@echo "🚀 To start the development server:"
|
||||
@echo " just dev"
|
||||
@@ -104,7 +99,7 @@ help:
|
||||
|
||||
|
||||
# Enhance demo site (build-time content injection)
|
||||
enhance input="test-sites/demo-site" output="dist":
|
||||
enhance input="demos/default" output="dist":
|
||||
./insertr enhance {{input}} --output {{output}} --mock
|
||||
|
||||
# === Content API Server Commands ===
|
||||
@@ -124,7 +119,7 @@ health port="8080":
|
||||
@echo "🔍 Checking API server health..."
|
||||
@curl -s http://localhost:{{port}}/health | jq . || echo "❌ Server not responding at localhost:{{port}}"
|
||||
|
||||
# Clean all build artifacts and backups
|
||||
# Clean all build artifacts
|
||||
clean:
|
||||
rm -rf lib/dist
|
||||
rm -rf insertr
|
||||
@@ -134,7 +129,7 @@ clean:
|
||||
rm -rf lib/node_modules
|
||||
rm -f dev.db
|
||||
rm -f insertr.db
|
||||
@echo "🧹 Cleaned all build artifacts and backups"
|
||||
@echo "🧹 Cleaned all build artifacts"
|
||||
|
||||
|
||||
|
||||
@@ -163,7 +158,7 @@ status:
|
||||
@echo "\n🔧 Unified binary:"
|
||||
@ls -la insertr main.go cmd/ internal/ 2>/dev/null || echo " Missing unified binary components"
|
||||
@echo "\n🌐 Demo site:"
|
||||
@ls -la test-sites/demo-site/index.html test-sites/demo-site/about.html 2>/dev/null || echo " Missing demo files"
|
||||
@ls -la demos/demo-site/index.html demos/demo-site/about.html 2>/dev/null || echo " Missing demo files"
|
||||
@echo ""
|
||||
@echo "🚀 Development Commands:"
|
||||
@echo " just dev - Full-stack development (recommended)"
|
||||
@@ -191,53 +186,44 @@ clean-demos:
|
||||
echo "========================================="
|
||||
|
||||
# Demo directories
|
||||
if [ -d "./test-sites/demo-site_enhanced" ]; then
|
||||
rm -rf "./test-sites/demo-site_enhanced"
|
||||
echo "🗑️ Removed: demo-site_enhanced"
|
||||
if [ -d "./demos/default_enhanced" ]; then
|
||||
rm -rf "./demos/default_enhanced"
|
||||
echo "🗑️ Removed: default_enhanced"
|
||||
fi
|
||||
|
||||
if [ -d "./test-sites/simple/dan-eden-portfolio_enhanced" ]; then
|
||||
rm -rf "./test-sites/simple/dan-eden-portfolio_enhanced"
|
||||
if [ -d "./demos/simple_enhanced" ]; then
|
||||
rm -rf "./demos/simple_enhanced"
|
||||
echo "🗑️ Removed: simple_enhanced"
|
||||
fi
|
||||
|
||||
if [ -d "./demos/dan-eden-portfolio_enhanced" ]; then
|
||||
rm -rf "./demos/dan-eden-portfolio_enhanced"
|
||||
echo "🗑️ Removed: dan-eden-portfolio_enhanced"
|
||||
fi
|
||||
|
||||
if [ -d "./test-sites/simple/test-simple_enhanced" ]; then
|
||||
rm -rf "./test-sites/simple/test-simple_enhanced"
|
||||
echo "🗑️ Removed: test-simple_enhanced"
|
||||
fi
|
||||
|
||||
# Clean up any temporary directories
|
||||
if [ -d "./test-sites/simple/dan-eden-portfolio-temp" ]; then
|
||||
rm -rf "./test-sites/simple/dan-eden-portfolio-temp"
|
||||
if [ -d "./demos/dan-eden-portfolio-temp" ]; then
|
||||
rm -rf "./demos/dan-eden-portfolio-temp"
|
||||
echo "🗑️ Removed: dan-eden-portfolio-temp"
|
||||
fi
|
||||
|
||||
if [ -d "./test-sites/simple/test-simple-temp" ]; then
|
||||
rm -rf "./test-sites/simple/test-simple-temp"
|
||||
echo "🗑️ Removed: test-simple-temp"
|
||||
if [ -d "./demos/simple-temp" ]; then
|
||||
rm -rf "./demos/simple-temp"
|
||||
echo "🗑️ Removed: simple-temp"
|
||||
fi
|
||||
|
||||
# Legacy directories (cleanup from old workflow)
|
||||
for legacy_dir in dan-eden-portfolio-auto-enhanced dan-eden-portfolio-full dan-eden-portfolio-auto dan-eden-portfolio-auto-v2 dan-eden-portfolio-auto-enhanced test-simple-auto-enhanced test-simple-full dan-eden-portfolio-enhanced test-simple-enhanced; do
|
||||
if [ -d "./test-sites/simple/${legacy_dir}" ]; then
|
||||
rm -rf "./test-sites/simple/${legacy_dir}"
|
||||
for legacy_dir in demo-site demo-site_enhanced demo_enhanced simple/test-simple simple/dan-eden-portfolio; do
|
||||
if [ -d "./demos/${legacy_dir}" ]; then
|
||||
rm -rf "./demos/${legacy_dir}"
|
||||
echo "🗑️ Removed: ${legacy_dir} (legacy)"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -d "./test-sites/simple/dan-eden-portfolio-full" ]; then
|
||||
rm -rf "./test-sites/simple/dan-eden-portfolio-full"
|
||||
echo "🗑️ Removed: dan-eden-portfolio-full"
|
||||
fi
|
||||
|
||||
if [ -d "./test-sites/simple/test-simple-auto-enhanced" ]; then
|
||||
rm -rf "./test-sites/simple/test-simple-auto-enhanced"
|
||||
echo "🗑️ Removed: test-simple-auto-enhanced"
|
||||
fi
|
||||
|
||||
if [ -d "./test-sites/simple/test-simple-full" ]; then
|
||||
rm -rf "./test-sites/simple/test-simple-full"
|
||||
echo "🗑️ Removed: test-simple-full"
|
||||
# Clean up legacy directories in simple subdirectory
|
||||
if [ -d "./demos/simple" ] && [ -z "$(find ./demos/simple -maxdepth 1 -name '*.html' -o -name '*.yaml')" ]; then
|
||||
# If simple directory exists but contains no HTML/YAML files, it's legacy
|
||||
rm -rf "./demos/simple"/* 2>/dev/null || true
|
||||
fi
|
||||
|
||||
|
||||
|
||||
@@ -10,9 +10,8 @@
|
||||
"src/"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "rollup -c && npm run copy:demo",
|
||||
"build": "rollup -c",
|
||||
"build:only": "rollup -c",
|
||||
"copy:demo": "cp dist/insertr.js ../test-sites/demo-site/insertr.js",
|
||||
"watch": "rollup -c -w",
|
||||
"dev": "rollup -c -w"
|
||||
},
|
||||
|
||||
@@ -2,20 +2,7 @@ import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||
import terser from '@rollup/plugin-terser';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
// Simple copy plugin to auto-copy to demo-site during development
|
||||
function copyToDemo() {
|
||||
return {
|
||||
name: 'copy-to-demo',
|
||||
writeBundle() {
|
||||
try {
|
||||
execSync('cp dist/insertr.js ../test-sites/demo-site/insertr.js');
|
||||
console.log('📄 Copied to test-sites/demo-site/insertr.js');
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Failed to copy to test-sites/demo-site:', error.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
// No longer needed - insertr.js served via CDN endpoint
|
||||
|
||||
export default [
|
||||
// Development build
|
||||
@@ -27,8 +14,7 @@ export default [
|
||||
name: 'Insertr'
|
||||
},
|
||||
plugins: [
|
||||
nodeResolve(),
|
||||
copyToDemo()
|
||||
nodeResolve()
|
||||
]
|
||||
},
|
||||
// Production build (minified)
|
||||
|
||||
@@ -39,8 +39,8 @@ const commands = {
|
||||
|
||||
// Check files exist
|
||||
const requiredFiles = [
|
||||
'test-sites/demo-site/index.html',
|
||||
'test-sites/demo-site/about.html',
|
||||
'demos/demo-site/index.html',
|
||||
'demos/demo-site/about.html',
|
||||
'lib/dist/insertr.js',
|
||||
'lib/dist/insertr.min.js',
|
||||
'cmd/serve.go',
|
||||
@@ -77,8 +77,8 @@ const commands = {
|
||||
console.log('\n📊 Project stats:');
|
||||
|
||||
// Count editable elements
|
||||
const indexContent = fs.readFileSync('test-sites/demo-site/index.html', 'utf8');
|
||||
const aboutContent = fs.readFileSync('test-sites/demo-site/about.html', 'utf8');
|
||||
const indexContent = fs.readFileSync('demos/demo-site/index.html', 'utf8');
|
||||
const aboutContent = fs.readFileSync('demos/demo-site/about.html', 'utf8');
|
||||
const insertrMatches = (indexContent + aboutContent).match(/class="insertr"/g) || [];
|
||||
console.log(` 📝 Editable elements: ${insertrMatches.length}`);
|
||||
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
{
|
||||
"site": {
|
||||
"id": "acme-consulting",
|
||||
"domain": "acmeconsulting.example.com",
|
||||
"owner": "client@acmeconsulting.com",
|
||||
"created": "2024-01-15T10:30:00Z",
|
||||
"updated": "2024-01-29T14:22:00Z"
|
||||
},
|
||||
"content": {
|
||||
"nav-logo": {
|
||||
"type": "simple",
|
||||
"value": "Acme Consulting",
|
||||
"updated": "2024-01-15T10:30:00Z",
|
||||
"updatedBy": "client@acmeconsulting.com"
|
||||
},
|
||||
"hero-content": {
|
||||
"type": "rich",
|
||||
"value": "# Transform Your Business with Expert Consulting\n\nWe help small businesses grow through strategic planning, process optimization, and digital transformation. Our team brings 15+ years of experience to drive your success.\n\n[Get Started Today](contact.html)",
|
||||
"updated": "2024-01-20T09:15:00Z",
|
||||
"updatedBy": "client@acmeconsulting.com"
|
||||
},
|
||||
"services-title": {
|
||||
"type": "rich",
|
||||
"value": "## Our Services\n\nComprehensive solutions tailored to your business needs",
|
||||
"updated": "2024-01-15T10:30:00Z",
|
||||
"updatedBy": "client@acmeconsulting.com"
|
||||
},
|
||||
"service-strategy": {
|
||||
"type": "rich",
|
||||
"value": "### Strategic Planning\n\nDevelop clear roadmaps and actionable strategies that align with your business goals and drive sustainable growth.",
|
||||
"updated": "2024-01-15T10:30:00Z",
|
||||
"updatedBy": "client@acmeconsulting.com"
|
||||
},
|
||||
"service-operations": {
|
||||
"type": "rich",
|
||||
"value": "### Operations Optimization\n\nStreamline processes, reduce costs, and improve efficiency through proven methodologies and best practices.",
|
||||
"updated": "2024-01-15T10:30:00Z",
|
||||
"updatedBy": "client@acmeconsulting.com"
|
||||
},
|
||||
"service-digital": {
|
||||
"type": "rich",
|
||||
"value": "### Digital Transformation\n\nModernize your technology stack and digital presence to compete effectively in today's marketplace.",
|
||||
"updated": "2024-01-15T10:30:00Z",
|
||||
"updatedBy": "client@acmeconsulting.com"
|
||||
},
|
||||
"testimonial-content": {
|
||||
"type": "rich",
|
||||
"value": "> \"Acme Consulting transformed our operations completely. We saw a 40% increase in efficiency within 6 months of implementing their recommendations.\"\n> \n> — Sarah Johnson, CEO of TechStart Inc.",
|
||||
"updated": "2024-01-25T16:45:00Z",
|
||||
"updatedBy": "client@acmeconsulting.com"
|
||||
},
|
||||
"cta-content": {
|
||||
"type": "rich",
|
||||
"value": "## Ready to Transform Your Business?\n\nContact us today for a free consultation and discover how we can help you achieve your goals.\n\n[Schedule Consultation](contact.html)",
|
||||
"updated": "2024-01-15T10:30:00Z",
|
||||
"updatedBy": "client@acmeconsulting.com"
|
||||
},
|
||||
"footer-info": {
|
||||
"type": "simple",
|
||||
"value": "© 2024 Acme Consulting Services. All rights reserved.\n📧 info@acmeconsulting.com | 📞 (555) 123-4567",
|
||||
"updated": "2024-01-15T10:30:00Z",
|
||||
"updatedBy": "client@acmeconsulting.com"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"client@acmeconsulting.com": {
|
||||
"role": "editor",
|
||||
"canEdit": ["*"],
|
||||
"canDelete": false,
|
||||
"canCreatePages": false
|
||||
},
|
||||
"developer@example.com": {
|
||||
"role": "admin",
|
||||
"canEdit": ["*"],
|
||||
"canDelete": true,
|
||||
"canCreatePages": true
|
||||
}
|
||||
},
|
||||
"history": [
|
||||
{
|
||||
"contentId": "testimonial-content",
|
||||
"timestamp": "2024-01-25T16:45:00Z",
|
||||
"user": "client@acmeconsulting.com",
|
||||
"action": "update",
|
||||
"previousValue": "> \"Working with Acme Consulting was a game-changer for our business.\"\n> \n> — Sarah Johnson, CEO of TechStart Inc."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
<!DOCTYPE html><html><head>
|
||||
<title>Simple Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="insertr" data-content-id="index-h1-e0f926" data-content-type="text">Welcome</h1>
|
||||
<p class="insertr" data-content-id="index-p-b376ed" data-content-type="markdown">This is a <strong>test</strong> paragraph with <a href="/">a link</a>.</p>
|
||||
<div class="insertr" data-content-id="index-div-e90881" data-content-type="markdown">
|
||||
<h2>Section Title</h2>
|
||||
<p>Another paragraph here.</p>
|
||||
<button>Click Me</button>
|
||||
</div>
|
||||
|
||||
|
||||
</body></html>
|
||||
@@ -1,27 +0,0 @@
|
||||
# Insertr Configuration for Simple Demo Site
|
||||
# Specific configuration for the simple test site demo
|
||||
|
||||
# Global settings
|
||||
dev_mode: true # Development mode for demos
|
||||
|
||||
# Database configuration
|
||||
database:
|
||||
path: "./insertr.db" # Shared database with main config
|
||||
|
||||
# Demo-specific configuration
|
||||
demo:
|
||||
site_id: "simple" # Unique site ID for simple demo
|
||||
inject_demo_gate: true # Auto-inject demo gate if no gates exist
|
||||
mock_auth: true # Use mock authentication for demos
|
||||
api_endpoint: "http://localhost:8080/api/content"
|
||||
demo_port: 3000 # Port for live-server
|
||||
|
||||
# CLI enhancement configuration
|
||||
cli:
|
||||
site_id: "simple" # Site ID for this demo
|
||||
output: "./simple-demo" # Output directory for enhanced files
|
||||
inject_demo_gate: true # Inject demo gate in development mode
|
||||
|
||||
# Authentication configuration (for demo)
|
||||
auth:
|
||||
provider: "mock" # Mock auth for demos
|
||||
@@ -1,15 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Simple Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome</h1>
|
||||
<p>This is a <strong>test</strong> paragraph with <a href="/">a link</a>.</p>
|
||||
<div>
|
||||
<h2>Section Title</h2>
|
||||
<p>Another paragraph here.</p>
|
||||
<button>Click Me</button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,69 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Test script for unified content engine architecture
|
||||
echo "🔧 Testing Unified Content Engine Architecture"
|
||||
echo
|
||||
|
||||
# Test data
|
||||
HTML_MARKUP='<h2 class="hero-title">Welcome to Our Site</h2>'
|
||||
SITE_ID="demo"
|
||||
FILE_PATH="index.html"
|
||||
CONTENT_VALUE="Welcome to Our Amazing Website"
|
||||
CONTENT_TYPE="text"
|
||||
|
||||
echo "📝 Test Data:"
|
||||
echo " HTML Markup: $HTML_MARKUP"
|
||||
echo " Site ID: $SITE_ID"
|
||||
echo " File Path: $FILE_PATH"
|
||||
echo " Content: $CONTENT_VALUE"
|
||||
echo
|
||||
|
||||
# Create JSON payload
|
||||
JSON_PAYLOAD=$(cat <<EOF
|
||||
{
|
||||
"html_markup": "$HTML_MARKUP",
|
||||
"file_path": "$FILE_PATH",
|
||||
"site_id": "$SITE_ID",
|
||||
"value": "$CONTENT_VALUE",
|
||||
"type": "$CONTENT_TYPE"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
echo "🌐 Testing API endpoint..."
|
||||
echo "POST http://localhost:8080/api/content"
|
||||
echo
|
||||
|
||||
# Test the API
|
||||
RESPONSE=$(curl -s -X POST \
|
||||
http://localhost:8080/api/content \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer mock-token" \
|
||||
-d "$JSON_PAYLOAD" 2>/dev/null)
|
||||
|
||||
if [ $? -eq 0 ] && [ -n "$RESPONSE" ]; then
|
||||
echo "✅ API Response:"
|
||||
echo "$RESPONSE" | jq '.' 2>/dev/null || echo "$RESPONSE"
|
||||
echo
|
||||
|
||||
# Extract ID from response if possible
|
||||
CONTENT_ID=$(echo "$RESPONSE" | jq -r '.id' 2>/dev/null)
|
||||
if [ "$CONTENT_ID" != "null" ] && [ -n "$CONTENT_ID" ]; then
|
||||
echo "🎯 Generated Content ID: $CONTENT_ID"
|
||||
echo
|
||||
|
||||
# Test retrieval
|
||||
echo "🔍 Testing content retrieval..."
|
||||
GET_RESPONSE=$(curl -s "http://localhost:8080/api/content/$CONTENT_ID?site_id=$SITE_ID" 2>/dev/null)
|
||||
echo "GET Response:"
|
||||
echo "$GET_RESPONSE" | jq '.' 2>/dev/null || echo "$GET_RESPONSE"
|
||||
fi
|
||||
else
|
||||
echo "❌ API Request Failed or Server Not Running"
|
||||
echo "Response: $RESPONSE"
|
||||
echo
|
||||
echo "💡 Start the server with: just dev"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "🏁 Test Complete"
|
||||