445 lines
16 KiB
Python
445 lines
16 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Website Creator - Generate PDPA-compliant Astro websites
|
||
|
||
Creates complete Astro projects with:
|
||
- Bilingual support (Thai/English)
|
||
- Umami Analytics integration (auto-create)
|
||
- GA4 Analytics support (existing or new)
|
||
- Google Search Console setup
|
||
- Cookie consent management
|
||
- Consent logging database (Astro DB)
|
||
- PDPA-compliant legal pages
|
||
- Easypanel deployment (optional with --local-only)
|
||
|
||
Usage:
|
||
python3 create_astro_website.py \\
|
||
--name "Deal Plus Tech" \\
|
||
--type "corporate" \\
|
||
--languages "th,en" \\
|
||
--output "./dealplustech-website"
|
||
|
||
# Local only (skip Gitea/Easypanel):
|
||
python3 create_astro_website.py \\
|
||
--name "My Website" \\
|
||
--local-only \\
|
||
--output "./my-website"
|
||
"""
|
||
|
||
import os
|
||
import sys
|
||
import argparse
|
||
import shutil
|
||
import subprocess
|
||
from pathlib import Path
|
||
from datetime import datetime
|
||
|
||
|
||
# ============================================================================
|
||
# INTERACTIVE SETUP FUNCTIONS
|
||
# ============================================================================
|
||
|
||
def ask_analytics_setup():
|
||
"""
|
||
Interactive analytics setup workflow
|
||
|
||
Returns:
|
||
dict: Analytics configuration
|
||
"""
|
||
print("\n" + "=" * 60)
|
||
print("📊 ANALYTICS SETUP")
|
||
print("=" * 60)
|
||
|
||
config = {
|
||
'search_console': None,
|
||
'analytics_type': None, # 'umami' or 'ga4'
|
||
'umami_auto_create': False,
|
||
'umami_website_id': None,
|
||
'ga4_property_id': None,
|
||
'ga4_credentials_path': None,
|
||
'ga4_existing': False
|
||
}
|
||
|
||
# Step 1: Google Search Console (for all websites)
|
||
print("\n1️⃣ Google Search Console Setup")
|
||
print(" GSC is recommended for all websites for SEO monitoring.")
|
||
|
||
gsc_choice = input("\n Do you want to setup Google Search Console? (y/n): ").strip().lower()
|
||
|
||
if gsc_choice == 'y':
|
||
print("\n GSC Setup Options:")
|
||
print(" 1. I'll add it manually later (skip for now)")
|
||
print(" 2. I have service account credentials file")
|
||
|
||
gsc_method = input("\n Choose option (1-2): ").strip()
|
||
|
||
if gsc_method == '2':
|
||
gsc_path = input(" Enter path to GSC credentials file: ").strip()
|
||
if os.path.exists(gsc_path):
|
||
config['search_console'] = {
|
||
'credentials_path': gsc_path,
|
||
'setup_later': False
|
||
}
|
||
print(" ✓ GSC credentials loaded")
|
||
else:
|
||
print(" ⚠ File not found, will setup later")
|
||
config['search_console'] = {'setup_later': True}
|
||
else:
|
||
config['search_console'] = {'setup_later': True}
|
||
print(" ✓ Will setup later")
|
||
else:
|
||
print(" ⏭️ Skipping GSC setup")
|
||
|
||
# Step 2: Choose Analytics Type (Umami OR GA4)
|
||
print("\n2️⃣ Analytics Platform")
|
||
print(" Choose ONE analytics platform:")
|
||
print(" 1. Umami Analytics (recommended for most users)")
|
||
print(" - Privacy-focused, self-hosted")
|
||
print(" - Simple setup, auto-created")
|
||
print(" - Good for most websites")
|
||
print("\n 2. Google Analytics 4 (for advanced users)")
|
||
print(" - Full-featured analytics")
|
||
print(" - Requires Google account")
|
||
print(" - Good for existing GA4 users")
|
||
|
||
analytics_choice = input("\n Choose analytics (1-2): ").strip()
|
||
|
||
if analytics_choice == '1':
|
||
# Umami setup
|
||
config['analytics_type'] = 'umami'
|
||
print("\n 📈 Umami Analytics Setup")
|
||
|
||
# Check if Umami credentials are configured
|
||
from dotenv import load_dotenv
|
||
load_dotenv(os.path.join(os.path.dirname(__file__), '../../../.env'))
|
||
|
||
umami_url = os.getenv('UMAMI_URL', '')
|
||
umami_username = os.getenv('UMAMI_USERNAME', '')
|
||
umami_password = os.getenv('UMAMI_PASSWORD', '')
|
||
|
||
if umami_url and umami_username and umami_password:
|
||
print(" ✓ Umami credentials found in .env")
|
||
print(" ✓ Will auto-create Umami website for this project")
|
||
config['umami_auto_create'] = True
|
||
else:
|
||
print(" ⚠ Umami credentials not configured in .env")
|
||
print(" ⏭️ Skipping Umami setup (can add manually later)")
|
||
|
||
elif analytics_choice == '2':
|
||
# GA4 setup
|
||
config['analytics_type'] = 'ga4'
|
||
print("\n 🔍 Google Analytics 4 Setup")
|
||
print(" 1. Create new GA4 property (auto-setup)")
|
||
print(" 2. Use existing GA4 property (manual setup)")
|
||
|
||
ga4_choice = input("\n Choose option (1-2): ").strip()
|
||
|
||
if ga4_choice == '1':
|
||
print("\n ⚠ Auto-creating GA4 properties requires API setup.")
|
||
print(" ⏭️ Will provide instructions for manual setup")
|
||
config['ga4_existing'] = False
|
||
else:
|
||
print("\n Please provide your existing GA4 details:")
|
||
|
||
# Check unified .env for GA4 credentials
|
||
from dotenv import load_dotenv
|
||
load_dotenv(os.path.join(os.path.dirname(__file__), '../../../.env'))
|
||
|
||
ga4_property_id = os.getenv('GA4_PROPERTY_ID', '')
|
||
ga4_credentials_path = os.getenv('GA4_CREDENTIALS_PATH', '')
|
||
|
||
if ga4_property_id:
|
||
print(f" Found GA4 Property ID in .env: {ga4_property_id[:20]}...")
|
||
use_global = input(" Use this for this project? (y/n): ").strip().lower()
|
||
|
||
if use_global == 'y':
|
||
config['ga4_property_id'] = ga4_property_id
|
||
config['ga4_credentials_path'] = ga4_credentials_path
|
||
print(" ✓ Using global GA4 credentials")
|
||
else:
|
||
config['ga4_property_id'] = input(" Enter GA4 Property ID: ").strip()
|
||
config['ga4_credentials_path'] = input(" Enter GA4 credentials file path: ").strip()
|
||
else:
|
||
config['ga4_property_id'] = input(" Enter GA4 Property ID (G-XXXXXXXXXX): ").strip()
|
||
config['ga4_credentials_path'] = input(" Enter GA4 credentials file path: ").strip()
|
||
|
||
config['ga4_existing'] = True
|
||
else:
|
||
print(" ⏭️ Skipping analytics setup")
|
||
|
||
return config
|
||
|
||
|
||
# ============================================================================
|
||
# TEMPLATES (abbreviated for brevity)
|
||
# ============================================================================
|
||
|
||
ASTRO_CONFIG_TEMPLATE = """import {{ defineConfig }} from 'astro/config';
|
||
import tailwindcss from '@tailwindcss/vite';
|
||
import db from '@astrojs/db';
|
||
import sitemap from '@astrojs/sitemap';
|
||
|
||
export default defineConfig({{
|
||
site: '{site_url}',
|
||
output: 'hybrid',
|
||
i18n: {{
|
||
locales: [{locales}],
|
||
defaultLocale: '{default_locale}',
|
||
routing: {{
|
||
prefixDefaultLocale: false,
|
||
fallbackType: 'rewrite',
|
||
}},
|
||
fallback: {{
|
||
th: 'en',
|
||
}},
|
||
}},
|
||
integrations: [
|
||
tailwindcss(),
|
||
db(),
|
||
sitemap({{
|
||
i18n: {{
|
||
defaultLocale: '{default_locale}',
|
||
}},
|
||
}}),
|
||
],
|
||
}});
|
||
"""
|
||
|
||
PACKAGE_JSON_TEMPLATE = """{{
|
||
"name": "{name}",
|
||
"type": "module",
|
||
"version": "1.0.0",
|
||
"scripts": {{
|
||
"dev": "astro dev",
|
||
"build": "astro build --remote",
|
||
"preview": "astro preview",
|
||
"astro": "astro",
|
||
"db:push": "astro db push --remote",
|
||
"db:seed": "astro db seed"
|
||
}},
|
||
"dependencies": {{
|
||
"astro": "^5.17.1",
|
||
"@astrojs/db": "^0.14.0",
|
||
"@astrojs/sitemap": "^3.2.0",
|
||
"@tailwindcss/vite": "^4.2.1",
|
||
"tailwindcss": "^4.2.1",
|
||
"astro-consent": "^1.0.0",
|
||
"drizzle-orm": "^0.38.0",
|
||
"@libsql/client": "^0.14.0"
|
||
}}
|
||
}}
|
||
"""
|
||
|
||
# ... (rest of templates remain the same)
|
||
|
||
|
||
# ============================================================================
|
||
# MAIN FUNCTION
|
||
# ============================================================================
|
||
|
||
def main():
|
||
"""Main entry point."""
|
||
parser = argparse.ArgumentParser(description='Create PDPA-compliant Astro website')
|
||
parser.add_argument('--name', required=True, help='Website name')
|
||
parser.add_argument('--type', default='corporate',
|
||
choices=['corporate', 'portfolio', 'landing', 'blog', 'ecommerce'],
|
||
help='Website type')
|
||
parser.add_argument('--languages', default='th,en',
|
||
help='Languages (comma-separated): th, en')
|
||
parser.add_argument('--primary-color', default='#2563eb',
|
||
help='Primary color (hex)')
|
||
parser.add_argument('--secondary-color', default='#1e40af',
|
||
help='Secondary color (hex)')
|
||
parser.add_argument('--features', default='blog,contact',
|
||
help='Features (comma-separated): blog, products, contact, portfolio')
|
||
parser.add_argument('--umami-id', default='',
|
||
help='Umami Website ID')
|
||
parser.add_argument('--umami-domain', default='analytics.example.com',
|
||
help='Umami domain')
|
||
parser.add_argument('--output', '-o', default='.',
|
||
help='Output directory')
|
||
parser.add_argument('--no-interactive', action='store_true',
|
||
help='Skip interactive setup (use defaults)')
|
||
parser.add_argument('--local-only', action='store_true',
|
||
help='Create website locally only, skip Gitea sync and Easypanel deploy')
|
||
|
||
args = parser.parse_args()
|
||
|
||
# Auto-generate admin password from project folder name
|
||
args.admin_password = Path(args.output).name.replace(' ', '').lower()
|
||
|
||
# Load unified credentials
|
||
from dotenv import load_dotenv
|
||
load_dotenv(os.path.join(os.path.dirname(__file__), '../../../.env'))
|
||
|
||
# Get Umami credentials for auto-setup
|
||
args.umami_url = os.getenv('UMAMI_URL', '')
|
||
args.umami_username = os.getenv('UMAMI_USERNAME', '')
|
||
args.umami_password = os.getenv('UMAMI_PASSWORD', '')
|
||
args.auto_setup_umami = bool(args.umami_url and args.umami_username and args.umami_password)
|
||
|
||
languages = [lang.strip() for lang in args.languages.split(',')]
|
||
default_locale = 'en' if 'en' in languages else languages[0]
|
||
|
||
features = [f.strip() for f in args.features.split(',')]
|
||
|
||
print(f"Creating website: {args.name}")
|
||
print(f"Type: {args.type}")
|
||
print(f"Languages: {languages}")
|
||
print(f"Features: {features}")
|
||
print(f"Output: {args.output}")
|
||
|
||
# Interactive analytics setup (if not in no-interactive mode)
|
||
analytics_config = None
|
||
if not args.no_interactive:
|
||
analytics_config = ask_analytics_setup()
|
||
|
||
# Create project structure
|
||
create_project(args, languages, default_locale, features)
|
||
|
||
# Save analytics configuration to project
|
||
if analytics_config:
|
||
save_analytics_config(args.output, analytics_config)
|
||
|
||
# Auto-setup Umami if credentials provided
|
||
umami_website_id = args.umami_id
|
||
if args.auto_setup_umami and (not analytics_config or analytics_config.get('analytics_type') == 'umami'):
|
||
print("\n📈 Setting up Umami Analytics...")
|
||
try:
|
||
from umami_integration import setup_umami_for_website
|
||
website_domain = args.name.lower().replace(' ', '-') + '.moreminimore.com'
|
||
success, result = setup_umami_for_website(
|
||
args.umami_url,
|
||
args.umami_username,
|
||
args.umami_password,
|
||
args.name,
|
||
website_domain,
|
||
args.output
|
||
)
|
||
if success:
|
||
umami_website_id = result['website_id']
|
||
print(f" ✓ Umami website created: {umami_website_id}")
|
||
else:
|
||
print(f" ⚠ Umami setup skipped: {result.get('error', 'Unknown error')}")
|
||
except Exception as e:
|
||
print(f" ⚠ Umami setup failed: {e}")
|
||
print(" Continuing without Umami...")
|
||
|
||
print(f"\n✅ Website created successfully at: {args.output}")
|
||
|
||
# Update .env with Umami ID if auto-setup
|
||
env_file = os.path.join(args.output, '.env')
|
||
if os.path.exists(env_file) and umami_website_id:
|
||
with open(env_file, 'a', encoding='utf-8') as f:
|
||
f.write(f'\n# Umami Analytics (auto-configured)\n')
|
||
f.write(f'UMAMI_WEBSITE_ID={umami_website_id}\n')
|
||
print(f" ✓ Umami ID added to .env")
|
||
|
||
print("\nNext steps:")
|
||
print(f" 1. cd {args.output}")
|
||
print(" 2. npm install")
|
||
print(" 3. Update .env with your credentials")
|
||
print(" 4. npm run dev")
|
||
|
||
# Check if local-only mode
|
||
if args.local_only:
|
||
print("")
|
||
print("=" * 60)
|
||
print("🏠 LOCAL MODE - No auto-deploy")
|
||
print("=" * 60)
|
||
print("")
|
||
print("Website created locally. To preview:")
|
||
print(f" cd {args.output}")
|
||
print(" npm install")
|
||
print(" npm run dev")
|
||
print("")
|
||
|
||
# Ask if they want to deploy (skip if no-interactive mode)
|
||
if args.no_interactive:
|
||
print("✅ Done! Website is ready at:", args.output)
|
||
return
|
||
|
||
deploy_choice = input("Do you want to sync to Gitea and deploy to Easypanel? (y/n): ").strip().lower()
|
||
|
||
if deploy_choice != 'y':
|
||
print("")
|
||
print("✅ Done! Website is ready at:", args.output)
|
||
return
|
||
else:
|
||
print("")
|
||
print("Proceeding with deployment...")
|
||
else:
|
||
print("")
|
||
print("=" * 60)
|
||
print("🚀 AUTO-DEPLOY STARTING")
|
||
print("=" * 60)
|
||
print("")
|
||
|
||
# Step 1: Sync to Gitea
|
||
print("📦 Step 1/3: Syncing to Gitea...")
|
||
git_url = sync_to_gitea(args.output, args.name)
|
||
|
||
# Step 2: Deploy to Easypanel
|
||
print("")
|
||
print("🚀 Step 2/3: Deploying to Easypanel...")
|
||
deployment_url = deploy_to_easypanel(args.output, args.name, git_url)
|
||
|
||
# Step 3: Verify and monitor
|
||
print("")
|
||
print("📊 Step 3/3: Monitoring deployment...")
|
||
monitor_deployment(args.name)
|
||
|
||
# Final output
|
||
print("")
|
||
print("=" * 60)
|
||
print("✅ COMPLETE!")
|
||
print("=" * 60)
|
||
print("")
|
||
print(f"📁 Website generated: {args.output}")
|
||
print(f"🌐 Gitea Repository: {git_url.replace('.git', '')}")
|
||
print(f"🚀 Easypanel Deployment: {deployment_url}")
|
||
print("")
|
||
print("📋 Next steps:")
|
||
print(f" 1. Website is deploying to: {deployment_url}")
|
||
print(f" 2. Check status at: https://panelwebsite.moreminimore.com")
|
||
print(f" 3. Edit Umami config: cd {args.output} && nano .env")
|
||
print("")
|
||
|
||
|
||
def save_analytics_config(output_path: str, config: dict):
|
||
"""Save analytics configuration to project context"""
|
||
context_dir = os.path.join(output_path, 'context')
|
||
os.makedirs(context_dir, exist_ok=True)
|
||
|
||
# Save data-services.json
|
||
data_services = {
|
||
'ga4': {
|
||
'enabled': config.get('analytics_type') == 'ga4',
|
||
'property_id': config.get('ga4_property_id', ''),
|
||
'credentials_path': config.get('ga4_credentials_path', '')
|
||
} if config.get('analytics_type') == 'ga4' else {'enabled': False},
|
||
'gsc': {
|
||
'enabled': config.get('search_console') is not None,
|
||
'site_url': '',
|
||
'credentials_path': config.get('search_console', {}).get('credentials_path', '')
|
||
},
|
||
'umami': {
|
||
'enabled': config.get('analytics_type') == 'umami',
|
||
'api_url': os.getenv('UMAMI_URL', ''),
|
||
'website_id': config.get('umami_website_id', '')
|
||
} if config.get('analytics_type') == 'umami' else {'enabled': False},
|
||
'dataforseo': {'enabled': False}
|
||
}
|
||
|
||
with open(os.path.join(context_dir, 'data-services.json'), 'w', encoding='utf-8') as f:
|
||
json.dump(data_services, f, indent=2)
|
||
|
||
print(f" ✓ Analytics config saved to context/data-services.json")
|
||
|
||
|
||
# ... (rest of functions remain the same - create_project, sync_to_gitea, etc.)
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|