339 lines
11 KiB
Text
339 lines
11 KiB
Text
|
|
#!/usr/bin/env nu
|
||
|
|
# Development workflow script
|
||
|
|
|
||
|
|
use ../config/build.config.nu
|
||
|
|
use build.nu build_page
|
||
|
|
use utils.nu *
|
||
|
|
use optimize-html.nu *
|
||
|
|
use optimize-css.nu *
|
||
|
|
use optimize-svg.nu *
|
||
|
|
|
||
|
|
# Development server with auto-rebuild
|
||
|
|
def main [
|
||
|
|
--port(-p): int = 8080 # Development server port
|
||
|
|
--watch(-w) # Enable file watching
|
||
|
|
--no-browser(-n) # Don't open browser automatically
|
||
|
|
--verbose(-v) # Verbose output
|
||
|
|
] {
|
||
|
|
let config = (load_config)
|
||
|
|
|
||
|
|
print "🚀 Starting development environment..."
|
||
|
|
|
||
|
|
# Initial build
|
||
|
|
print "📦 Building development version..."
|
||
|
|
try {
|
||
|
|
build_page --dev
|
||
|
|
} catch {
|
||
|
|
print "❌ Build failed"
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
if $watch {
|
||
|
|
# Start server and file watcher with better server detection
|
||
|
|
start_dev_server_with_watch $config $port $no_browser $verbose
|
||
|
|
} else {
|
||
|
|
# Start development server only
|
||
|
|
start_dev_server $config.dist_dir $port $no_browser
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# File watcher for auto-rebuild
|
||
|
|
def start_file_watcher [config: record, verbose: bool] {
|
||
|
|
print "👁️ Starting file watcher..."
|
||
|
|
|
||
|
|
let watch_files = [
|
||
|
|
$"($config.src_dir)/index.html",
|
||
|
|
$"($config.css_src)/modules/*.css",
|
||
|
|
$"($config.css_src)/main.css",
|
||
|
|
$"($config.css_src)/svg-data.css",
|
||
|
|
$"($config.svg_src)/*.svg"
|
||
|
|
]
|
||
|
|
|
||
|
|
# Expand glob patterns
|
||
|
|
let expanded_files = ($watch_files | each { |pattern|
|
||
|
|
if ($pattern | str contains "*") {
|
||
|
|
glob $pattern
|
||
|
|
} else {
|
||
|
|
[$pattern]
|
||
|
|
}
|
||
|
|
} | flatten | where ($it | path exists))
|
||
|
|
|
||
|
|
print $"📂 Watching ($expanded_files | length) files for changes..."
|
||
|
|
|
||
|
|
# Watch in background job
|
||
|
|
watch_files $expanded_files { |files|
|
||
|
|
print "\n🔄 Files changed!"
|
||
|
|
print "🔨 Rebuilding..."
|
||
|
|
|
||
|
|
# Quick development build
|
||
|
|
try {
|
||
|
|
build_page --dev
|
||
|
|
print "✅ Rebuild completed"
|
||
|
|
} catch {
|
||
|
|
print "❌ Rebuild failed"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Start development server
|
||
|
|
def start_dev_server [directory: string, port: int, no_browser: bool] {
|
||
|
|
print $"🌐 Starting development server..."
|
||
|
|
print $"📁 Serving: ($directory)"
|
||
|
|
print $"🔗 URL: http://localhost:($port)"
|
||
|
|
|
||
|
|
if not $no_browser {
|
||
|
|
# Try to open browser (platform-specific)
|
||
|
|
try {
|
||
|
|
if ($nu.os-info.name == "macos") {
|
||
|
|
open $"http://localhost:($port)"
|
||
|
|
} else if ($nu.os-info.name == "linux") {
|
||
|
|
xdg-open $"http://localhost:($port)"
|
||
|
|
} else if ($nu.os-info.name == "windows") {
|
||
|
|
start $"http://localhost:($port)"
|
||
|
|
}
|
||
|
|
} catch {
|
||
|
|
print $"💡 Open http://localhost:($port) in your browser"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Start simple HTTP server
|
||
|
|
serve_dev $port $directory
|
||
|
|
}
|
||
|
|
|
||
|
|
# Start development server with file watching
|
||
|
|
def start_dev_server_with_watch [config: record, port: int, no_browser: bool, verbose: bool] {
|
||
|
|
print $"🌐 Starting development server with file watching..."
|
||
|
|
print $"📁 Serving: ($config.dist_dir)"
|
||
|
|
print $"🔗 URL: http://localhost:($port)"
|
||
|
|
|
||
|
|
# Check what HTTP servers are available
|
||
|
|
let has_simple_server = (which simple-http-server | length) > 0
|
||
|
|
let has_python3 = (which python3 | length) > 0
|
||
|
|
let has_python = (which python | length) > 0
|
||
|
|
|
||
|
|
if $has_simple_server or $has_python3 or $has_python {
|
||
|
|
print ""
|
||
|
|
print "🚀 Starting HTTP server in background..."
|
||
|
|
|
||
|
|
# Start HTTP server in background
|
||
|
|
let server_cmd = if $has_simple_server {
|
||
|
|
print " Using simple-http-server (recommended)"
|
||
|
|
$"cd ($config.dist_dir) && simple-http-server --port ($port) --index"
|
||
|
|
} else if $has_python3 {
|
||
|
|
print " Using Python3 HTTP server"
|
||
|
|
$"cd ($config.dist_dir) && python3 -m http.server ($port)"
|
||
|
|
} else {
|
||
|
|
print " Using Python2 HTTP server"
|
||
|
|
$"cd ($config.dist_dir) && python -m SimpleHTTPServer ($port)"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Start server in background using shell
|
||
|
|
try {
|
||
|
|
^bash -c $"($server_cmd) > /dev/null 2>&1 &"
|
||
|
|
print $"📡 HTTP server started on port ($port)"
|
||
|
|
} catch { |err|
|
||
|
|
print $"❌ Failed to start server: ($err.msg)"
|
||
|
|
print " Try starting manually:"
|
||
|
|
print $" ($server_cmd)"
|
||
|
|
}
|
||
|
|
|
||
|
|
if not $no_browser {
|
||
|
|
print "🌐 Opening browser..."
|
||
|
|
|
||
|
|
# Try to open browser (platform-specific) without delay to avoid Ctrl+C issues
|
||
|
|
try {
|
||
|
|
if ($nu.os-info.name == "macos") {
|
||
|
|
open $"http://localhost:($port)"
|
||
|
|
} else if ($nu.os-info.name == "linux") {
|
||
|
|
xdg-open $"http://localhost:($port)"
|
||
|
|
} else if ($nu.os-info.name == "windows") {
|
||
|
|
start $"http://localhost:($port)"
|
||
|
|
}
|
||
|
|
} catch {
|
||
|
|
print $"💡 Manually open http://localhost:($port) in your browser"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
print "👁️ File watcher will now monitor for changes..."
|
||
|
|
print $"🌐 Server running at: http://localhost:($port)"
|
||
|
|
print ""
|
||
|
|
print "💡 Usage:"
|
||
|
|
print " • Files will auto-rebuild when changed"
|
||
|
|
print " • Press Ctrl+C to stop (error message is normal)"
|
||
|
|
print $" • Cleanup server when done: nu scripts/dev.nu cleanup ($port)"
|
||
|
|
print ""
|
||
|
|
|
||
|
|
# Start file watcher - let it run until interrupted
|
||
|
|
# The error message on Ctrl+C is normal Nushell behavior
|
||
|
|
start_file_watcher $config $verbose
|
||
|
|
} else {
|
||
|
|
print "❌ No HTTP server found."
|
||
|
|
print " Install one of the following:"
|
||
|
|
print " - simple-http-server: cargo install basic-http-server"
|
||
|
|
print " - Python: Install via system package manager"
|
||
|
|
print $" Then restart the development server."
|
||
|
|
|
||
|
|
# Still run file watcher for builds
|
||
|
|
start_file_watcher $config $verbose
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Quick development build
|
||
|
|
def "main build" [] {
|
||
|
|
print "🔨 Quick development build..."
|
||
|
|
try {
|
||
|
|
build_page --dev
|
||
|
|
print "✅ Build completed"
|
||
|
|
} catch { |err|
|
||
|
|
print $"❌ Build failed: ($err)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Clean development build
|
||
|
|
def "main clean" [] {
|
||
|
|
let config = (load_config)
|
||
|
|
print "🧹 Cleaning development build..."
|
||
|
|
|
||
|
|
if ($config.dist_dir | path exists) {
|
||
|
|
rm -rf $config.dist_dir
|
||
|
|
}
|
||
|
|
|
||
|
|
print "✅ Clean completed"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Development build with analysis
|
||
|
|
def "main analyze" [] {
|
||
|
|
print "📊 Building with analysis..."
|
||
|
|
try {
|
||
|
|
build_page --dev --analyze --verbose
|
||
|
|
print "✅ Analysis completed"
|
||
|
|
} catch { |err|
|
||
|
|
print $"❌ Analysis failed: ($err)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Lint and validate source files
|
||
|
|
def "main lint" [] {
|
||
|
|
let config = (load_config)
|
||
|
|
print "🔍 Linting source files..."
|
||
|
|
|
||
|
|
# Validate HTML
|
||
|
|
let html_file = $"($config.src_dir)/index.html"
|
||
|
|
if ($html_file | path exists) {
|
||
|
|
let html_content = (open $html_file)
|
||
|
|
let validation = (validate_html_structure $html_content)
|
||
|
|
|
||
|
|
if $validation.valid_structure {
|
||
|
|
print "✅ HTML structure is valid"
|
||
|
|
} else {
|
||
|
|
print "❌ HTML structure issues found:"
|
||
|
|
if not $validation.has_doctype { print " - Missing DOCTYPE" }
|
||
|
|
if not $validation.has_title { print " - Missing title tag" }
|
||
|
|
if not $validation.has_head { print " - Missing head section" }
|
||
|
|
if not $validation.has_body { print " - Missing body tag" }
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Validate CSS modules
|
||
|
|
let css_modules = (glob $"($config.css_src)/modules/*.css")
|
||
|
|
print $"🎨 Checking ($css_modules | length) CSS modules..."
|
||
|
|
|
||
|
|
$css_modules | each { |css_file|
|
||
|
|
let css_content = (open $css_file)
|
||
|
|
let analysis = (analyze_css $css_content)
|
||
|
|
|
||
|
|
print $" ($css_file | path basename): ($analysis.size_bytes) bytes, ($analysis.rule_lines) rules"
|
||
|
|
|
||
|
|
# Check for common issues
|
||
|
|
if ($css_content | str contains "!important") {
|
||
|
|
print $" ⚠️ Contains !important declarations"
|
||
|
|
}
|
||
|
|
if ($css_content | str contains "position: absolute") and (($css_content | str length) > 500) {
|
||
|
|
print $" ⚠️ Heavy use of absolute positioning"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Validate SVG files
|
||
|
|
let svg_files = (glob $"($config.svg_src)/*.svg")
|
||
|
|
print $"🖼️ Checking ($svg_files | length) SVG files..."
|
||
|
|
|
||
|
|
$svg_files | each { |svg_file|
|
||
|
|
let svg_content = (open $svg_file)
|
||
|
|
let validation = (validate_svg $svg_content)
|
||
|
|
|
||
|
|
if $validation.valid {
|
||
|
|
print $" ✅ ($svg_file | path basename): Valid"
|
||
|
|
} else {
|
||
|
|
print $" ❌ ($svg_file | path basename): Invalid SVG"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
print "🔍 Lint completed"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Preview production build locally
|
||
|
|
def "main preview" [--port(-p): int = 8080] {
|
||
|
|
print "📦 Building production version for preview..."
|
||
|
|
try {
|
||
|
|
build_page --clean --verbose --analyze
|
||
|
|
let config = (load_config)
|
||
|
|
print "🌐 Starting preview server..."
|
||
|
|
serve_dev $port $config.dist_dir
|
||
|
|
} catch { |err|
|
||
|
|
print $"❌ Preview failed: ($err)"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Cleanup development server processes
|
||
|
|
def cleanup_dev_server [port: int] {
|
||
|
|
print "🧹 Cleaning up background processes..."
|
||
|
|
|
||
|
|
# Kill any servers running on this port
|
||
|
|
let cleanup_commands = [
|
||
|
|
$"pkill -f 'http.server ($port)'",
|
||
|
|
$"pkill -f 'simple-http-server.*--port ($port)'",
|
||
|
|
$"pkill -f 'SimpleHTTPServer ($port)'",
|
||
|
|
$"lsof -ti:($port) | xargs kill -9"
|
||
|
|
]
|
||
|
|
|
||
|
|
$cleanup_commands | each { |cmd|
|
||
|
|
try {
|
||
|
|
^bash -c $cmd
|
||
|
|
} catch {
|
||
|
|
# Ignore errors - process might not exist
|
||
|
|
null
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
print "✅ Cleanup completed"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Manual cleanup command
|
||
|
|
def "main cleanup" [port?: int = 8080] {
|
||
|
|
cleanup_dev_server $port
|
||
|
|
}
|
||
|
|
|
||
|
|
# Development help
|
||
|
|
def "main help" [] {
|
||
|
|
print "🛠️ Development Commands:"
|
||
|
|
print "========================"
|
||
|
|
print " nu scripts/dev.nu - Start development server with file watching"
|
||
|
|
print " nu scripts/dev.nu build - Quick development build"
|
||
|
|
print " nu scripts/dev.nu clean - Clean development build"
|
||
|
|
print " nu scripts/dev.nu analyze - Build with file size analysis"
|
||
|
|
print " nu scripts/dev.nu lint - Lint and validate source files"
|
||
|
|
print " nu scripts/dev.nu preview - Preview production build locally"
|
||
|
|
print " nu scripts/dev.nu cleanup <PORT> - Manually stop background servers"
|
||
|
|
print " nu scripts/dev.nu help - Show this help"
|
||
|
|
print ""
|
||
|
|
print "Options:"
|
||
|
|
print " --port, -p <PORT> - Development server port (default: 8080)"
|
||
|
|
print " --watch, -w - Enable file watching for auto-rebuild"
|
||
|
|
print " --no-browser, -n - Don't open browser automatically"
|
||
|
|
print " --verbose, -v - Verbose output"
|
||
|
|
print ""
|
||
|
|
print "Examples:"
|
||
|
|
print " nu scripts/dev.nu --watch --port 3000"
|
||
|
|
print " nu scripts/dev.nu preview --port 8080"
|
||
|
|
print " nu scripts/dev.nu analyze"
|
||
|
|
}
|