#!/usr/bin/env python3 import os import sys import argparse import shutil from pathlib import Path SCRIPT_DIR = Path(__file__).parent def pkg_json(name): return ( '''{ "name": "''' + name + """", "type": "module", "version": "1.0.0", "scripts": { "dev": "astro dev", "build": "astro build", "preview": "astro preview", "astro": "astro" }, "dependencies": { "astro": "^5.17.1", "@astrojs/react": "^4.2.0", "@astrojs/node": "^9.1.0", "@astrojs/sitemap": "^3.2.0", "@supabase/supabase-js": "^2.47.0", "@supabase/ssr": "^0.6.1", "@tailwindcss/vite": "^4.2.1", "tailwindcss": "^4.2.1", "zustand": "^5.0.0", "react": "^19.0.0", "react-dom": "^19.0.0", "jose": "^6.0.0" }, "devDependencies": { "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "typescript": "^5.7.0" } }""" ) ASTRO_CONFIG = """import {{ defineConfig }} from 'astro/config'; import react from '@astrojs/react'; import node from '@astrojs/node'; import sitemap from '@astrojs/sitemap'; import tailwindcss from '@tailwindcss/vite'; export default defineConfig({{ site: '{site_url}', output: 'hybrid', adapter: node({{ mode: 'standalone' }}), i18n: {{ locales: [{locales}], defaultLocale: '{default_locale}', routing: {{ prefixDefaultLocale: false, fallbackType: 'rewrite', }}, }}, integrations: [ react(), sitemap({{ i18n: {{ defaultLocale: '{default_locale}' }}, }}), ], vite: {{ plugins: [tailwindcss()], }}, }}); """ TSCONFIG = """{ "extends": "astro/tsconfigs/strict", "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "react", "baseUrl": ".", "paths": { "@/*": ["src/*"] } } }""" SUPABASE_TS = """import {{ createClient }} from '@supabase/supabase-js'; import type {{ Database }} from './types'; const supabaseUrl = import.meta.env.SUPABASE_URL; const supabaseAnonKey = import.meta.env.SUPABASE_ANON_KEY; export const supabase = createClient(supabaseUrl, supabaseAnonKey); export type {{ Database }}; """ AUTH_TS = r"""import { supabase } from './supabase'; import { SignJWT, jwtVerify } from 'jose'; import type { User, VendorProfile } from './types'; const JWT_SECRET = new TextEncoder().encode( import.meta.env.JWT_SECRET || 'default-secret-change-me' ); export interface AuthUser { id: string; email: string; name: string | null; role: 'customer' | 'vendor' | 'admin'; vendor_id?: string; } export async function createSessionToken(user: AuthUser): Promise { return new SignJWT({ sub: user.id, email: user.email, name: user.name, role: user.role, vendor_id: user.vendor_id, }) .setProtectedHeader({ alg: 'HS256' }) .setIssuedAt() .setExpirationTime('7d') .sign(JWT_SECRET); } export async function verifySessionToken(token: string): Promise { try { const { payload } = await jwtVerify(token, JWT_SECRET); return { id: payload.sub as string, email: payload.email as string, name: payload.name as string | null, role: payload.role as 'customer' | 'vendor' | 'admin', vendor_id: payload.vendor_id as string | undefined, }; } catch { return null; } } export async function registerUser( email: string, password: string, name: string ): Promise<{ user: User | null; error: string | null }> { const { data, error } = await supabase.auth.signUp({ email, password, options: { data: { name }, }, }); if (error) return { user: null, error: error.message }; const user = data.user; if (!user) return { user: null, error: 'Registration failed' }; return { user: { id: user.id, email: user.email || email, name, role: 'customer', }, error: null, }; } export async function loginUser( email: string, password: string ): Promise<{ user: AuthUser | null; token: string | null; error: string | null }> { const { data, error } = await supabase.auth.signInWithPassword({ email, password, }); if (error) return { user: null, token: null, error: error.message }; const user = data.user; if (!user) return { user: null, token: null, error: 'Login failed' }; const { data: profileData } = await supabase .from('vendor_profiles') .select('id') .eq('user_id', user.id) .single(); const authUser: AuthUser = { id: user.id, email: user.email || email, name: user.user_metadata?.name || null, role: profileData ? 'vendor' : 'customer', vendor_id: profileData?.id, }; const token = await createSessionToken(authUser); return { user: authUser, token, error: null }; } export async function logoutUser(): Promise { await supabase.auth.signOut(); } """ PAYSOS_TS = r"""export interface PaySoPayment { merchant_id: string; order_id: string; amount: number; currency: string; description: string; callback_url: string; return_url: string; customer_name?: string; customer_email?: string; customer_phone?: string; } export interface PaySoResponse { code: string; message: string; data: { payment_url: string; qr_code?: string; qr_image?: string; transaction_id: string; }; } export interface PaySoWebhookPayload { transaction_id: string; order_id: string; amount: number; status: 'pending' | 'success' | 'failed'; timestamp: string; signature: string; } export async function createPaySoPayment(payment: PaySoPayment): Promise { const response = await fetch('https://api.paysogateway.com/v1/payment', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${import.meta.env.PAYSOLO_API_KEY}`, }, body: JSON.stringify({ merchant_id: import.meta.env.PAYSOLO_MERCHANT_ID, order_id: payment.order_id, amount: payment.amount, currency: payment.currency || 'THB', description: payment.description, callback_url: payment.callback_url, return_url: payment.return_url, customer: { name: payment.customer_name, email: payment.customer_email, phone: payment.customer_phone, }, }), }); if (!response.ok) { throw new Error(`PaySo API error: ${response.status}`); } return response.json(); } export async function verifyPaySoSignature( payload: PaySoWebhookPayload, signature: string ): Promise { const crypto = await import('crypto'); const secret = import.meta.env.PAYSOLO_SECRET_KEY; const data = JSON.stringify({ transaction_id: payload.transaction_id, order_id: payload.order_id, amount: payload.amount, status: payload.status, }); const expectedSignature = crypto .createHmac('sha256', secret) .update(data) .digest('hex'); return signature === expectedSignature; } """ TYPES_TS = r"""export interface User { id: string; email: string; name: string | null; phone: string | null; role: 'customer' | 'vendor' | 'admin'; avatar_url: string | null; created_at: string; updated_at: string; } export interface VendorProfile { id: string; user_id: string; store_name: string; store_slug: string; store_description: string | null; store_logo: string | null; bank_account: string | null; bank_name: string | null; payout_status: 'pending' | 'approved' | 'rejected'; total_earnings: number; approved_at: string | null; created_at: string; } export interface Category { id: string; name: string; slug: string; description: string | null; image_url: string | null; parent_id: string | null; sort_order: number; } export interface Product { id: string; vendor_id: string; category_id: string | null; name: string; slug: string; description: string | null; price: number; compare_at_price: number | null; cost_price: number | null; sku: string | null; barcode: string | null; inventory: number; low_stock_threshold: number; track_inventory: boolean; allow_backorder: boolean; weight: number | null; images: string[]; metadata: Record; status: 'draft' | 'active' | 'archived'; featured: boolean; created_at: string; updated_at: string; } export interface ProductVariant { id: string; product_id: string; name: string; sku: string | null; price: number | null; inventory: number; attributes: Record; image_url: string | null; } export interface Review { id: string; product_id: string; user_id: string; order_id: string | null; rating: number; title: string | null; comment: string | null; images: string[]; verified_purchase: boolean; status: 'pending' | 'approved' | 'rejected'; created_at: string; } export interface Order { id: string; order_number: string; user_id: string; vendor_id: string | null; status: 'pending' | 'confirmed' | 'processing' | 'shipped' | 'delivered' | 'cancelled' | 'refunded'; payment_status: 'unpaid' | 'paid' | 'failed' | 'refunded'; subtotal: number; tax: number; shipping_cost: number; total: number; currency: string; payment_method: string | null; payment_provider: string | null; payment_ref: string | null; shipping_name: string | null; shipping_phone: string | null; shipping_address: string | null; shipping_city: string | null; shipping_postal: string | null; shipping_country: string; notes: string | null; created_at: string; updated_at: string; } export interface OrderItem { id: string; order_id: string; product_id: string; variant_id: string | null; vendor_id: string | null; quantity: number; unit_price: number; total_price: number; } export interface CartItem { id: string; product: Product; variant: ProductVariant | null; quantity: number; } """ UTILS_TS = r"""export function formatPrice(amount: number, currency = 'THB'): string { return new Intl.NumberFormat('th-TH', { style: 'currency', currency, }).format(amount); } export function generateSlug(text: string): string { return text .toLowerCase() .replace(/[^\w\s-]/g, '') .replace(/[\s_-]+/g, '-') .replace(/^-+|-+$/g, ''); } export function generateOrderNumber(): string { const date = new Date(); const dateStr = date.toISOString().slice(0, 10).replace(/-/g, ''); const random = Math.random().toString(36).substring(2, 10).toUpperCase(); return `ORD-${dateStr}-${random}`; } export function cn(...classes: (string | undefined | null | false)[]): string { return classes.filter(Boolean).join(' '); } export function debounce any>( fn: T, delay: number ): (...args: Parameters) => void { let timeoutId: ReturnType; return (...args: Parameters) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => fn(...args), delay); }; } """ CART_STORE = r"""import { create } from 'zustand'; import { persist } from 'zustand/middleware'; import type { CartItem, Product, ProductVariant } from '../lib/types'; interface CartStore { items: CartItem[]; addItem: (product: Product, variant?: ProductVariant, quantity?: number) => void; removeItem: (productId: string, variantId?: string) => void; updateQuantity: (productId: string, variantId: string | undefined, quantity: number) => void; clearCart: () => void; getTotal: () => number; getItemCount: () => number; } export const useCartStore = create()( persist( (set, get) => ({ items: [], addItem: (product, variant, quantity = 1) => { set((state) => { const existingIndex = state.items.findIndex( (item) => item.product.id === product.id && item.variant?.id === variant?.id ); if (existingIndex >= 0) { const newItems = [...state.items]; newItems[existingIndex].quantity += quantity; return { items: newItems }; } return { items: [ ...state.items, { id: crypto.randomUUID(), product, variant: variant || null, quantity }, ], }; }); }, removeItem: (productId, variantId) => { set((state) => ({ items: state.items.filter( (item) => !(item.product.id === productId && item.variant?.id === variantId) ), })); }, updateQuantity: (productId, variantId, quantity) => { if (quantity <= 0) { get().removeItem(productId, variantId); return; } set((state) => ({ items: state.items.map((item) => item.product.id === productId && item.variant?.id === variantId ? { ...item, quantity } : item ), })); }, clearCart: () => set({ items: [] }), getTotal: () => { return get().items.reduce( (total, item) => total + (item.variant?.price || item.product.price) * item.quantity, 0 ); }, getItemCount: () => { return get().items.reduce((count, item) => count + item.quantity, 0); }, }), { name: 'ecommerce-cart', } ) ); """ AUTH_STORE = r"""import { create } from 'zustand'; import { persist } from 'zustand/middleware'; import type { AuthUser } from '../lib/auth'; interface AuthStore { user: AuthUser | null; token: string | null; isLoading: boolean; setAuth: (user: AuthUser, token: string) => void; logout: () => void; setLoading: (loading: boolean) => void; } export const useAuthStore = create()( persist( (set) => ({ user: null, token: null, isLoading: false, setAuth: (user, token) => set({ user, token }), logout: () => set({ user: null, token: null }), setLoading: (isLoading) => set({ isLoading }), }), { name: 'ecommerce-auth', } ) ); """ VENDOR_STORE = r"""import { create } from 'zustand'; import { supabase } from '../lib/supabase'; import type { Product, Order, VendorProfile } from '../lib/types'; interface VendorStore { profile: VendorProfile | null; products: Product[]; orders: Order[]; isLoading: boolean; fetchProfile: (userId: string) => Promise; fetchProducts: (vendorId: string) => Promise; fetchOrders: (vendorId: string) => Promise; createProduct: (product: Partial) => Promise; updateProduct: (id: string, updates: Partial) => Promise; updateOrderStatus: (orderId: string, status: string) => Promise; } export const useVendorStore = create((set, get) => ({ profile: null, products: [], orders: [], isLoading: false, fetchProfile: async (userId) => { set({ isLoading: true }); const { data } = await supabase .from('vendor_profiles') .select('*') .eq('user_id', userId) .single(); set({ profile: data, isLoading: false }); }, fetchProducts: async (vendorId) => { set({ isLoading: true }); const { data } = await supabase .from('products') .select('*') .eq('vendor_id', vendorId) .order('created_at', { ascending: false }); set({ products: data || [], isLoading: false }); }, fetchOrders: async (vendorId) => { set({ isLoading: true }); const { data } = await supabase .from('orders') .select('*') .eq('vendor_id', vendorId) .order('created_at', { ascending: false }); set({ orders: data || [], isLoading: false }); }, createProduct: async (product) => { const { data, error } = await supabase .from('products') .insert(product) .select() .single(); if (error) return null; set((state) => ({ products: [data, ...state.products] })); return data; }, updateProduct: async (id, updates) => { await supabase.from('products').update(updates).eq('id', id); set((state) => ({ products: state.products.map((p) => p.id === id ? { ...p, ...updates } : p ), })); }, updateOrderStatus: async (orderId, status) => { await supabase.from('orders').update({ status }).eq('id', orderId); set((state) => ({ orders: state.orders.map((o) => o.id === orderId ? { ...o, status: status as Order['status'] } : o ), })); }, })); """ BASE_LAYOUT = """--- interface Props {{ title: string; description?: string; }} const {{ title, description = '{site_name}' }} = Astro.props; --- {{title}} | {site_name} """ TH_JSON = """{{ "common": {{ "home": "หน้าแรก", "products": "สินค้า", "cart": "ตะกร้า", "checkout": "ชำระเงิน", "login": "เข้าสู่ระบบ", "register": "ลงทะเบียน", "logout": "ออกจากระบบ" }}, "product": {{ "addToCart": "เพิ่มลงตะกร้า", "outOfStock": "สินค้าหมด", "inStock": "มีสินค้า" }}, "cart": {{ "empty": "ตะกร้าว่าง", "total": "รวม" }} }}""" EN_JSON = """{{ "common": {{ "home": "Home", "products": "Products", "cart": "Cart", "checkout": "Checkout", "login": "Login", "register": "Register", "logout": "Logout" }}, "product": {{ "addToCart": "Add to Cart", "outOfStock": "Out of Stock", "inStock": "In Stock" }}, "cart": {{ "empty": "Your cart is empty", "total": "Total" }} }}""" GLOBAL_CSS = """@import "tailwindcss"; @theme { --font-sans: "Inter", system-ui, sans-serif; --color-primary: #2563eb; --color-secondary: #64748b; }""" CART_BUTTON = r"""import { useCartStore } from '../../stores/cart'; export function CartButton() { const getItemCount = useCartStore((state) => state.getItemCount); const count = getItemCount(); return ( {count > 0 && ( {count > 9 ? '9+' : count} )} ); }""" CART_DRAWER = r"""import { useState } from 'react'; import { useCartStore } from '../../stores/cart'; import { formatPrice } from '../../lib/utils'; import { CartItem } from './CartItem'; export function CartDrawer() { const [isOpen, setIsOpen] = useState(false); const items = useCartStore((state) => state.items); const getTotal = useCartStore((state) => state.getTotal); return ( <> {isOpen && (
setIsOpen(false)} />

ตะกร้าสินค้า

{items.length === 0 ? (

ตะกร้าว่างเปล่า

) : (
{items.map((item) => ( ))}
)}
{items.length > 0 && (
รวม {formatPrice(getTotal())}
ชำระเงิน
)}
)} ); }""" CART_ITEM_COMPONENT = r"""import { useCartStore } from '../../stores/cart'; import { formatPrice } from '../../lib/utils'; import type { CartItem as CartItemType } from '../../lib/types'; interface Props { item: CartItemType; } export function CartItem({ item }: Props) { const updateQuantity = useCartStore((state) => state.updateQuantity); const removeItem = useCartStore((state) => state.removeItem); const price = item.variant?.price || item.product.price; return (
{item.product.name}

{item.product.name}

{item.variant && (

{item.variant.name}

)}

{formatPrice(price)}

{item.quantity}
); }""" PRODUCT_CARD = r"""import { Link } from '@astrojs/react/components'; import { formatPrice } from '../../lib/utils'; import type { Product } from '../../lib/types'; interface Props { product: Product; } export function ProductCard({ product }: Props) { const isOutOfStock = product.inventory <= 0 && !product.allow_backorder; const isLowStock = product.inventory > 0 && product.inventory <= product.low_stock_threshold; return (
{product.name} {isOutOfStock && (
สินค้าหมด
)} {product.featured && !isOutOfStock && ( แนะนำ )}

{product.name}

{formatPrice(product.price)} {product.compare_at_price && ( {formatPrice(product.compare_at_price)} )}
{isLowStock && (

สินค้าใกล้หมด ({product.inventory} ชิ้น)

)}
); }""" CHECKOUT_FORM = r"""import { useState } from 'react'; import { useCartStore } from '../../stores/cart'; import { useAuthStore } from '../../stores/auth'; import { formatPrice, generateOrderNumber } from '../../lib/utils'; export function CheckoutForm() { const items = useCartStore((state) => state.items); const getTotal = useCartStore((state) => state.getTotal); const clearCart = useCartStore((state) => state.clearCart); const user = useAuthStore((state) => state.user); const [formData, setFormData] = useState({ name: user?.name || '', phone: '', address: '', city: '', postal: '', paymentMethod: 'qr', }); const [isSubmitting, setIsSubmitting] = useState(false); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setIsSubmitting(true); try { const response = await fetch('/api/checkout/create-order', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...formData, items: items.map((item) => ({ product_id: item.product.id, variant_id: item.variant?.id, quantity: item.quantity, unit_price: item.variant?.price || item.product.price, })), order_number: generateOrderNumber(), }), }); const data = await response.json(); if (data.payment_url) { window.location.href = data.payment_url; } else { clearCart(); window.location.href = `/orders/${data.order_id}`; } } catch (error) { console.error('Checkout error:', error); } finally { setIsSubmitting(false); } }; return (

ข้อมูลจัดส่ง

setFormData({ ...formData, name: e.target.value })} className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-primary focus:border-primary" />
setFormData({ ...formData, phone: e.target.value })} className="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-primary focus:border-primary" />