Files
opencode-skill/skills/website-creator/scripts/create_astro_website.py
Kunthawat Greethong 6e183c584b feat: Auto-generate admin password from project folder name
Changes:
- Removed --admin-password argument (no longer needed)
- Password auto-generated: folder_name.lower().replace(' ', '')
- Each website has unique password
- No need to ask user for password

Examples:
- moreminimore → password: moreminimore
- My Website → password: mywebsite
- deal-plustech → password: deal-plustech

Benefits:
- Simple (same as folder name)
- Secure (different per project)
- No password management needed
- Not stored in git (in .env)
2026-03-12 09:53:17 +07:00

410 lines
15 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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
Usage:
python3 create_astro_website.py \
--name "Deal Plus Tech" \
--type "corporate" \
--languages "th,en" \
--output "./dealplustech-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)')
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")
# Auto-deploy (always on)
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()