451 lines
No EOL
15 KiB
Text
451 lines
No EOL
15 KiB
Text
#!/usr/bin/env nu
|
||
# Production workflow script
|
||
|
||
use build.nu build_page
|
||
use utils.nu *
|
||
use optimize-html.nu *
|
||
use optimize-css.nu *
|
||
use optimize-svg.nu *
|
||
|
||
# Production build with full optimization
|
||
def main [
|
||
--analyze(-a) # Show detailed analysis
|
||
--compress(-c) # Generate compressed versions
|
||
--verbose(-v) # Verbose output
|
||
--output(-o): string # Custom output directory
|
||
] {
|
||
let config = if ($output | is-not-empty) {
|
||
(load_config | update dist_dir $output)
|
||
} else {
|
||
(load_config)
|
||
}
|
||
|
||
print "🚀 Starting production build..."
|
||
print $"📁 Output directory: ($config.dist_dir)"
|
||
|
||
# Validate configuration
|
||
if not (validate_config $config) {
|
||
print "❌ Configuration validation failed"
|
||
return
|
||
}
|
||
|
||
# Full production build
|
||
let build_result = (measure_time "Production build" {
|
||
build_page --clean --verbose --analyze
|
||
})
|
||
|
||
# Generate compressed versions if requested
|
||
if $compress {
|
||
generate_compressed_versions $config $verbose
|
||
}
|
||
|
||
# Performance audit
|
||
if $analyze {
|
||
performance_audit $config $verbose
|
||
}
|
||
|
||
# Generate deployment files
|
||
generate_deployment_files $config $verbose
|
||
|
||
print "✅ Production build completed successfully!"
|
||
|
||
# Show final summary
|
||
show_build_summary $config
|
||
}
|
||
|
||
# Generate compressed versions (gzip, brotli)
|
||
def generate_compressed_versions [config: record, verbose: bool] {
|
||
print "📦 Generating compressed versions..."
|
||
|
||
let files_to_compress = [
|
||
$"($config.dist_dir)/($config.html_output)",
|
||
$"($config.css_dist)/($config.css_output)",
|
||
$"($config.svg_dist)/sprites.svg"
|
||
]
|
||
|
||
let compressed_files = (compress_files $files_to_compress)
|
||
|
||
if $verbose {
|
||
print $"📦 Generated ($compressed_files | length) compressed files"
|
||
}
|
||
}
|
||
|
||
# Comprehensive performance audit
|
||
def performance_audit [config: record, verbose: bool] {
|
||
print "\n🔍 Performance Audit"
|
||
print "===================="
|
||
|
||
# HTML analysis
|
||
let html_file = $"($config.dist_dir)/($config.html_output)"
|
||
if ($html_file | path exists) {
|
||
let html_content = (open $html_file)
|
||
let html_analysis = (analyze_html $html_content)
|
||
let performance_hints = (generate_performance_hints $html_content)
|
||
|
||
print $"📄 HTML Analysis:"
|
||
print $" Size: (format_file_size $html_analysis.size_bytes)"
|
||
print $" Lines: ($html_analysis.lines)"
|
||
print $" External resources: CSS($html_analysis.external_css), JS($html_analysis.external_js), Images($html_analysis.images)"
|
||
|
||
if ($performance_hints | length) > 0 {
|
||
print $" 💡 Performance hints:"
|
||
$performance_hints | each { |hint| print $" - ($hint)" }
|
||
}
|
||
}
|
||
|
||
# CSS analysis
|
||
let css_file = $"($config.css_dist)/($config.css_output)"
|
||
if ($css_file | path exists) {
|
||
let css_content = (open $css_file)
|
||
let css_analysis = (analyze_css $css_content)
|
||
let css_metrics = (css_performance_metrics $css_content)
|
||
|
||
print $"\n🎨 CSS Analysis:"
|
||
print $" Size: (format_file_size $css_analysis.size_bytes)"
|
||
print $" Rules: ($css_analysis.rule_lines)"
|
||
print $" Selectors: ($css_metrics.total_selectors)"
|
||
print $" Properties: ($css_metrics.total_properties)"
|
||
print $" Avg properties per rule: ($css_metrics.avg_properties_per_rule)"
|
||
print $" Complex selectors: ($css_metrics.complex_selectors)"
|
||
}
|
||
|
||
# SVG analysis
|
||
let svg_files = (glob $"($config.svg_dist)/*.svg")
|
||
if ($svg_files | length) > 0 {
|
||
print $"\n🖼️ SVG Analysis:"
|
||
let svg_report = (svg_performance_report $svg_files)
|
||
|
||
$svg_report | each { |svg|
|
||
print $" ($svg.file | path basename): (format_file_size $svg.size)"
|
||
print $" Elements: ~($svg.elements), Paths: ~($svg.paths)"
|
||
print $" Animations: ($svg.animations), Valid: ($svg.valid)"
|
||
print $" Optimization potential: (format_file_size $svg.optimization_potential)"
|
||
}
|
||
}
|
||
|
||
# Overall performance score
|
||
calculate_performance_score $config
|
||
}
|
||
|
||
# Calculate overall performance score
|
||
def calculate_performance_score [config: record] {
|
||
let html_file = $"($config.dist_dir)/($config.html_output)"
|
||
let css_file = $"($config.css_dist)/($config.css_output)"
|
||
|
||
let total_size = ([$html_file, $css_file]
|
||
| where ($it | path exists)
|
||
| each { |file|
|
||
let size_raw = (ls $file | get size | first)
|
||
($size_raw | into int)
|
||
}
|
||
| math sum
|
||
)
|
||
|
||
let score = if $total_size < 50000 {
|
||
"A+ (Excellent)"
|
||
} else if $total_size < 100000 {
|
||
"A (Good)"
|
||
} else if $total_size < 200000 {
|
||
"B (Average)"
|
||
} else {
|
||
"C (Needs optimization)"
|
||
}
|
||
|
||
print $"\n⭐ Performance Score: ($score)"
|
||
print $" Total size: (format_file_size $total_size)"
|
||
|
||
# Recommendations based on size
|
||
if $total_size > 100000 {
|
||
print "💡 Recommendations:"
|
||
print " - Consider splitting CSS into critical/non-critical"
|
||
print " - Optimize SVG files further"
|
||
print " - Enable server compression (gzip/brotli)"
|
||
}
|
||
}
|
||
|
||
# Generate deployment files
|
||
def generate_deployment_files [config: record, verbose: bool] {
|
||
print "📋 Generating deployment files..."
|
||
|
||
# Ensure the output directory exists
|
||
if not ($config.dist_dir | path exists) {
|
||
mkdir $config.dist_dir
|
||
}
|
||
|
||
# Generate .htaccess for Apache
|
||
generate_htaccess $config
|
||
|
||
# Generate nginx.conf snippet
|
||
generate_nginx_config $config
|
||
|
||
# Generate deploy script
|
||
generate_deploy_script $config
|
||
|
||
# Generate robots.txt
|
||
generate_robots_txt $config
|
||
|
||
if $verbose {
|
||
print "📋 Generated deployment configuration files"
|
||
}
|
||
}
|
||
|
||
# Generate Apache .htaccess
|
||
def generate_htaccess [config: record] {
|
||
let htaccess_content = '# Page Build System - Apache Configuration
|
||
|
||
# Enable compression
|
||
<IfModule mod_deflate.c>
|
||
AddOutputFilterByType DEFLATE text/plain
|
||
AddOutputFilterByType DEFLATE text/html
|
||
AddOutputFilterByType DEFLATE text/xml
|
||
AddOutputFilterByType DEFLATE text/css
|
||
AddOutputFilterByType DEFLATE application/xml
|
||
AddOutputFilterByType DEFLATE application/xhtml+xml
|
||
AddOutputFilterByType DEFLATE application/rss+xml
|
||
AddOutputFilterByType DEFLATE application/javascript
|
||
AddOutputFilterByType DEFLATE application/x-javascript
|
||
AddOutputFilterByType DEFLATE image/svg+xml
|
||
</IfModule>
|
||
|
||
# Cache static assets
|
||
<IfModule mod_expires.c>
|
||
ExpiresActive on
|
||
ExpiresByType text/css "access plus 1 year"
|
||
ExpiresByType application/javascript "access plus 1 year"
|
||
ExpiresByType image/svg+xml "access plus 1 year"
|
||
ExpiresByType text/html "access plus 1 hour"
|
||
</IfModule>
|
||
|
||
# Security headers
|
||
<IfModule mod_headers.c>
|
||
Header always set X-Content-Type-Options nosniff
|
||
Header always set X-Frame-Options DENY
|
||
Header always set X-XSS-Protection "1; mode=block"
|
||
Header always set Content-Security-Policy "default-src '\''self'\''; style-src '\''self'\'' '\''unsafe-inline'\'' fonts.googleapis.com; font-src fonts.gstatic.com; img-src '\''self'\'' data:; script-src '\''none'\'';"
|
||
</IfModule>
|
||
|
||
# MIME types
|
||
AddType image/svg+xml .svg
|
||
AddType text/css .css'
|
||
|
||
$htaccess_content | save --force $"($config.dist_dir)/.htaccess"
|
||
}
|
||
|
||
# Generate Nginx configuration snippet
|
||
def generate_nginx_config [config: record] {
|
||
let nginx_content = '# Page Build System - Nginx Configuration
|
||
# Add this to your nginx server block
|
||
|
||
location / {
|
||
try_files $uri $uri/ =404;
|
||
|
||
# Cache static assets
|
||
location ~* \.(css|js|svg)$ {
|
||
expires 1y;
|
||
add_header Cache-Control "public, immutable";
|
||
}
|
||
|
||
# HTML files - shorter cache
|
||
location ~* \.html$ {
|
||
expires 1h;
|
||
add_header Cache-Control "public";
|
||
}
|
||
}
|
||
|
||
# Compression
|
||
gzip on;
|
||
gzip_vary on;
|
||
gzip_min_length 1024;
|
||
gzip_types
|
||
text/plain
|
||
text/css
|
||
text/xml
|
||
text/javascript
|
||
application/javascript
|
||
application/xml+rss
|
||
application/json
|
||
image/svg+xml;
|
||
|
||
# Security headers
|
||
add_header X-Content-Type-Options nosniff;
|
||
add_header X-Frame-Options DENY;
|
||
add_header X-XSS-Protection "1; mode=block";
|
||
add_header Content-Security-Policy "default-src '\''self'\''; style-src '\''self'\'' '\''unsafe-inline'\'' fonts.googleapis.com; font-src fonts.gstatic.com; img-src '\''self'\'' data:; script-src '\''none'\'';";'
|
||
|
||
$nginx_content | save --force $"($config.dist_dir)/nginx.conf"
|
||
}
|
||
|
||
# Generate deployment script
|
||
def generate_deploy_script [config: record] {
|
||
let deploy_script = '#!/bin/bash
|
||
# Deploy script for optimized web page
|
||
|
||
set -e
|
||
|
||
echo "🚀 Deploying optimized web page..."
|
||
|
||
# Configuration
|
||
DIST_DIR="dist"
|
||
REMOTE_HOST="${DEPLOY_HOST:-your-server.com}"
|
||
REMOTE_PATH="${DEPLOY_PATH:-/var/www/optimized-page}"
|
||
REMOTE_USER="${DEPLOY_USER:-deploy}"
|
||
|
||
# Check if dist directory exists
|
||
if [ ! -d "$DIST_DIR" ]; then
|
||
echo "❌ Distribution directory not found. Run build first."
|
||
exit 1
|
||
fi
|
||
|
||
# Deploy via rsync
|
||
echo "📦 Uploading files..."
|
||
rsync -avz --delete \
|
||
--exclude="*.map" \
|
||
--exclude="build-manifest.json" \
|
||
"$DIST_DIR/" "$REMOTE_USER@$REMOTE_HOST:$REMOTE_PATH/"
|
||
|
||
echo "✅ Deployment completed!"
|
||
echo "🌐 Site should be available at: https://$REMOTE_HOST"'
|
||
|
||
$deploy_script | save --force $"($config.dist_dir)/deploy.sh"
|
||
|
||
# Make executable
|
||
chmod +x $"($config.dist_dir)/deploy.sh"
|
||
}
|
||
|
||
# Generate robots.txt
|
||
def generate_robots_txt [config: record] {
|
||
let robots_content = 'User-agent: *
|
||
Allow: /
|
||
|
||
# Sitemap
|
||
Sitemap: https://your-domain.com/sitemap.xml'
|
||
|
||
$robots_content | save --force $"($config.dist_dir)/robots.txt"
|
||
}
|
||
|
||
# Show build summary
|
||
def show_build_summary [config: record] {
|
||
print "\n📊 Build Summary"
|
||
print "================"
|
||
|
||
let files = [
|
||
{ name: "index.html", path: $"($config.dist_dir)/($config.html_output)" },
|
||
{ name: "main.min.css", path: $"($config.css_dist)/($config.css_output)" },
|
||
{ name: "sprites.svg", path: $"($config.svg_dist)/sprites.svg" },
|
||
{ name: "symbols.svg", path: $"($config.svg_dist)/symbols.svg" }
|
||
]
|
||
|
||
$files | each { |file|
|
||
if ($file.path | path exists) {
|
||
let size_raw = (ls $file.path | get size | first)
|
||
let size = ($size_raw | into int)
|
||
let size_formatted = (format_file_size $size)
|
||
|
||
# Check for compressed version
|
||
let compressed_path = $"($file.path).gz"
|
||
let compressed_info = if ($compressed_path | path exists) {
|
||
let compressed_size_raw = (ls $compressed_path | get size | first)
|
||
let compressed_size = ($compressed_size_raw | into int)
|
||
let savings = (($size - $compressed_size) / $size * 100 | math round -p 1)
|
||
$" \(gzipped: (format_file_size $compressed_size), -($savings)%\)"
|
||
} else {
|
||
""
|
||
}
|
||
|
||
print $" 📄 ($file.name): ($size_formatted)($compressed_info)"
|
||
}
|
||
}
|
||
|
||
# Total uncompressed size
|
||
let total_size = ($files
|
||
| each { |file|
|
||
if ($file.path | path exists) {
|
||
let size_raw = (ls $file.path | get size | first)
|
||
($size_raw | into int)
|
||
} else {
|
||
0
|
||
}
|
||
}
|
||
| math sum
|
||
)
|
||
|
||
print $"\n📦 Total size: (format_file_size $total_size)"
|
||
|
||
# Build info
|
||
print $"\n📋 Build completed at: (date now | format date '%Y-%m-%d %H:%M:%S')"
|
||
print $"📁 Output directory: ($config.dist_dir)"
|
||
print $"🌐 Ready for deployment!"
|
||
}
|
||
|
||
# Quick production build
|
||
def "main quick" [
|
||
--analyze(-a) # Show detailed analysis
|
||
--compress(-c) # Generate compressed versions
|
||
--verbose(-v) # Verbose output
|
||
--output(-o): string # Custom output directory
|
||
] {
|
||
# Quick build defaults to verbose, user can override other flags
|
||
main --analyze=$analyze --compress=$compress --verbose=true --output=$output
|
||
}
|
||
|
||
# Production build with full analysis
|
||
def "main full" [
|
||
--analyze(-a) # Show detailed analysis
|
||
--compress(-c) # Generate compressed versions
|
||
--verbose(-v) # Verbose output
|
||
--output(-o): string # Custom output directory
|
||
] {
|
||
# Full build defaults to analyze+compress+verbose, user can override
|
||
let final_analyze = if $analyze != null { $analyze } else { true }
|
||
let final_compress = if $compress != null { $compress } else { true }
|
||
let final_verbose = if $verbose != null { $verbose } else { true }
|
||
|
||
main --analyze=$final_analyze --compress=$final_compress --verbose=$final_verbose --output=$output
|
||
}
|
||
|
||
# Deploy to staging
|
||
def "main staging" [
|
||
--analyze(-a) # Show detailed analysis
|
||
--compress(-c) # Generate compressed versions
|
||
--verbose(-v) # Verbose output
|
||
--output(-o): string # Custom output directory
|
||
] {
|
||
print "🚀 Building for staging..."
|
||
|
||
# Staging defaults to output="staging" and verbose=true, user can override
|
||
let final_output = if ($output | is-not-empty) { $output } else { "staging" }
|
||
let final_verbose = if $verbose != null { $verbose } else { true }
|
||
|
||
main --analyze=$analyze --compress=$compress --verbose=$final_verbose --output=$final_output
|
||
|
||
print $"📦 Staging build ready in ./($final_output)/"
|
||
}
|
||
|
||
# Production help
|
||
def "main help" [] {
|
||
print "🏭 Production Commands:"
|
||
print "======================="
|
||
print " nu prod.nu - Full production build (all flags)"
|
||
print " nu prod.nu quick - Quick production build"
|
||
print " nu prod.nu full - Full build with analysis and compression"
|
||
print " nu prod.nu staging - Build for staging environment"
|
||
print " nu prod.nu help - Show this help"
|
||
print ""
|
||
print "Main Command Flags:"
|
||
print " --analyze, -a - Show detailed performance analysis"
|
||
print " --compress, -c - Generate compressed versions (gzip)"
|
||
print " --verbose, -v - Verbose output"
|
||
print " --output, -o <DIR> - Custom output directory"
|
||
print ""
|
||
print "All subcommands support all flags:"
|
||
print " quick: defaults to --verbose"
|
||
print " full: defaults to --analyze --compress --verbose"
|
||
print " staging: defaults to --verbose --output staging"
|
||
print ""
|
||
print "Examples:"
|
||
print " nu prod.nu --analyze --compress # Main with all flags"
|
||
print " nu prod.nu --output staging # Main with custom output"
|
||
print " nu prod.nu quick -v -o test # Quick build to test dir"
|
||
print " nu prod.nu staging -a # Staging with analysis"
|
||
print " nu prod.nu full -o production # Full build to production dir"
|
||
} |