feat: migrate website-creator from Next.js+Payload to Astro+Tina CMS
Major changes: - Replace Payload CMS with Tina CMS (self-hosted) - Add Astro DB for consent logging (PDPA compliant) - Update Tailwind v3 to v4 (@tailwindcss/vite plugin) - Add astro-tina-starter template - Rewrite consent template for Astro (ConsentBanner.astro, Astro DB, Nano Stores) - Add install-tina-backend.sh for self-hosted Tina per customer - Rename convert-astro.sh to migrate-tina.sh - Add AGENTS.md template for generated websites - Delete all Payload/Next.js files Technical updates: - Astro DB using defineDb with eq operators for queries - Tailwind v4 with @theme block - Tina CMS local development mode - Proper Astro API routes for consent Research-verified with official documentation (April 2026)
This commit is contained in:
@@ -1,337 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
#===============================================================================
|
||||
# migrate-to-payload.sh - Migrate Astro content to Payload CMS with Lexical
|
||||
#
|
||||
# Usage: ./migrate-to-payload.sh [source-path] [target-path]
|
||||
#
|
||||
# This script migrates content from Astro MDX/Markdown to Payload CMS Lexical.
|
||||
# - Converts .md/.mdx files to Payload CMS Lexical JSON format
|
||||
# - Creates Payload collection entries
|
||||
# - Preserves frontmatter as collection fields
|
||||
#
|
||||
# Requirements:
|
||||
# - node.js 20+
|
||||
# - npm
|
||||
#
|
||||
#===============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
SOURCE_PATH="${1:-}"
|
||||
TARGET_PATH="${2:-.}"
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
print_usage() {
|
||||
cat << EOF
|
||||
Usage: $(basename "$0") [source-path] [target-path]
|
||||
|
||||
Migrate Astro content to Payload CMS with Lexical
|
||||
|
||||
Arguments:
|
||||
source-path Path to Astro project with content
|
||||
target-path Path to Next.js + Payload CMS project
|
||||
|
||||
Examples:
|
||||
$(basename "$0") /path/to/astro-site /path/to/payload-site
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
detect_content_type() {
|
||||
log_info "Detecting content structure..."
|
||||
|
||||
cd "$SOURCE_PATH"
|
||||
|
||||
if [ -d "src/content" ]; then
|
||||
CONTENT_DIR="src/content"
|
||||
elif [ -d "content" ]; then
|
||||
CONTENT_DIR="content"
|
||||
elif [ -d "src/pages" ]; then
|
||||
CONTENT_DIR="src/pages"
|
||||
else
|
||||
log_error "No content directory found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "Content directory: $CONTENT_DIR"
|
||||
}
|
||||
|
||||
backup_content() {
|
||||
log_info "Backing up content..."
|
||||
|
||||
BACKUP_DIR="/tmp/migration-backup-$(date +%s)"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
|
||||
if [ -d "$SOURCE_PATH/$CONTENT_DIR" ]; then
|
||||
cp -r "$SOURCE_PATH/$CONTENT_DIR" "$BACKUP_DIR/"
|
||||
fi
|
||||
|
||||
log_success "Backup at: $BACKUP_DIR"
|
||||
}
|
||||
|
||||
analyze_content() {
|
||||
log_info "Analyzing content..."
|
||||
|
||||
cd "$SOURCE_PATH"
|
||||
|
||||
local md_count=$(find "$CONTENT_DIR" -type f \( -name "*.md" -o -name "*.mdx" \) 2>/dev/null | wc -l)
|
||||
local astro_count=$(find . -type f -name "*.astro" 2>/dev/null | grep -v node_modules | wc -l)
|
||||
|
||||
echo ""
|
||||
echo " Content files: $md_count"
|
||||
echo " Astro components: $astro_count"
|
||||
echo ""
|
||||
|
||||
find "$CONTENT_DIR" -type f \( -name "*.md" -o -name "*.mdx" \) 2>/dev/null | head -20
|
||||
}
|
||||
|
||||
create_lexical_content() {
|
||||
log_info "Converting MDX to Payload CMS Lexical format..."
|
||||
|
||||
cd "$SOURCE_PATH"
|
||||
|
||||
local output_dir="$TARGET_PATH/src/content-migration"
|
||||
mkdir -p "$output_dir"
|
||||
|
||||
find "$CONTENT_DIR" -type f \( -name "*.md" -o -name "*.mdx" \) 2>/dev/null | while read -r file; do
|
||||
local relative_path="${file#$SOURCE_PATH/$CONTENT_DIR/}"
|
||||
local filename=$(basename "$file" .mdx .md | sed 's/\.mdx$//' | sed 's/\.md$//')
|
||||
local slug=$(echo "$filename" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')
|
||||
|
||||
local frontmatter=""
|
||||
local content=""
|
||||
|
||||
if grep -q "^---" "$file" 2>/dev/null; then
|
||||
frontmatter=$(sed -n '/^---/,/^---/p' "$file" | head -n -1 | tail -n +2)
|
||||
content=$(awk '/^---/{found=1; next} found' "$file")
|
||||
else
|
||||
content=$(cat "$file")
|
||||
fi
|
||||
|
||||
local title=$(echo "$frontmatter" | grep -i "^title:" | cut -d':' -f2- | tr -d ' "' | head -1)
|
||||
local date=$(echo "$frontmatter" | grep -i "^date:" | cut -d':' -f2- | tr -d ' "' | head -1)
|
||||
local description=$(echo "$frontmatter" | grep -i "^description:" | cut -d':' -f2- | tr -d ' "' | head -1)
|
||||
local author=$(echo "$frontmatter" | grep -i "^author:" | cut -d':' -f2- | tr -d ' "' | head -1)
|
||||
local image=$(echo "$frontmatter" | grep -i "^image:" | cut -d':' -f2- | tr -d ' "' | head -1)
|
||||
local tags=$(echo "$frontmatter" | grep -i "^tags:" | cut -d':' -f2- | tr -d '[]"' | head -1)
|
||||
|
||||
title=${title:-$filename}
|
||||
date=${date:-$(date +%Y-%m-%d)}
|
||||
|
||||
cat > "$output_dir/${slug}.json" << JSONEOF
|
||||
{
|
||||
"title": "$title",
|
||||
"slug": "$slug",
|
||||
"createdAt": "$date",
|
||||
"updatedAt": "$(date +%Y-%m-%d)",
|
||||
"meta": {
|
||||
"title": "$title",
|
||||
"description": "$description"
|
||||
},
|
||||
"author": "$author",
|
||||
"heroImage": "$image",
|
||||
"tags": ["$tags"],
|
||||
"content": {
|
||||
"root": {
|
||||
"type": "root",
|
||||
"format": "",
|
||||
"indent": 0,
|
||||
"version": 1,
|
||||
"children": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"version": 1,
|
||||
"children": [
|
||||
{
|
||||
"type": "text",
|
||||
"version": 1,
|
||||
"text": "$content",
|
||||
"mode": "tokenized",
|
||||
"style": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
JSONEOF
|
||||
|
||||
echo " Converted: $filename → $slug.json"
|
||||
done
|
||||
|
||||
log_success "Conversion complete: $output_dir/"
|
||||
}
|
||||
|
||||
create_payload_import_script() {
|
||||
log_info "Creating Payload import script..."
|
||||
|
||||
local output_dir="$TARGET_PATH/scripts"
|
||||
mkdir -p "$output_dir"
|
||||
|
||||
cat > "$output_dir/import-content.ts" << 'TSEOF'
|
||||
import { payload } from '../src/lib/payload'
|
||||
import { promises as fs } from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
async function importContent() {
|
||||
const contentDir = path.join(process.cwd(), 'src/content-migration')
|
||||
|
||||
try {
|
||||
const files = await fs.readdir(contentDir)
|
||||
const jsonFiles = files.filter(f => f.endsWith('.json'))
|
||||
|
||||
for (const file of jsonFiles) {
|
||||
const filePath = path.join(contentDir, file)
|
||||
const content = JSON.parse(await fs.readFile(filePath, 'utf-8'))
|
||||
|
||||
await payload.create({
|
||||
collection: 'posts',
|
||||
data: {
|
||||
title: content.title,
|
||||
slug: content.slug,
|
||||
createdAt: content.createdAt,
|
||||
updatedAt: content.updatedAt,
|
||||
meta: content.meta,
|
||||
author: content.author,
|
||||
heroImage: content.heroImage,
|
||||
tags: content.tags,
|
||||
content: content.content,
|
||||
_status: 'published',
|
||||
},
|
||||
})
|
||||
|
||||
console.log(`Imported: ${content.title}`)
|
||||
}
|
||||
|
||||
console.log(`\nSuccessfully imported ${jsonFiles.length} posts`)
|
||||
} catch (error) {
|
||||
console.error('Import failed:', error)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
importContent()
|
||||
TSEOF
|
||||
|
||||
log_success "Created: $output_dir/import-content.ts"
|
||||
}
|
||||
|
||||
create_migration_report() {
|
||||
log_info "Creating migration report..."
|
||||
|
||||
cd "$SOURCE_PATH"
|
||||
|
||||
local page_count=$(find "$CONTENT_DIR" -type f \( -name "*.md" -o -name "*.mdx" \) 2>/dev/null | wc -l)
|
||||
|
||||
cat > "$TARGET_PATH/MIGRATION_REPORT.md" << EOF
|
||||
# Migration Report: Astro → Payload CMS
|
||||
|
||||
## Source
|
||||
- **Type:** Astro
|
||||
- **Path:** $SOURCE_PATH
|
||||
- **Backup:** $BACKUP_DIR
|
||||
- **Date:** $(date)
|
||||
|
||||
## Statistics
|
||||
- **Total Posts:** $page_count
|
||||
|
||||
## Content Migration
|
||||
|
||||
Content has been converted to Payload CMS Lexical JSON format in:
|
||||
\`\`\`
|
||||
src/content-migration/
|
||||
\`\`\`
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Review converted content:**
|
||||
\`\`\`bash
|
||||
ls src/content-migration/
|
||||
\`\`\`
|
||||
|
||||
2. **Configure Payload collection:**
|
||||
Make sure you have a 'posts' collection in \`src/collections/Posts.ts\`
|
||||
|
||||
3. **Import content to Payload:**
|
||||
\`\`\`bash
|
||||
npx tsx scripts/import-content.ts
|
||||
\`\`\`
|
||||
|
||||
4. **Verify in admin:**
|
||||
- Go to http://localhost:3002/admin
|
||||
- Navigate to Posts collection
|
||||
- Verify content and rich text editor (Lexical)
|
||||
|
||||
## Notes
|
||||
|
||||
- MDX/Markdown content is converted to Lexical JSON format
|
||||
- Frontmatter fields (title, date, description) are mapped to collection fields
|
||||
- Complex MDX components need manual conversion in Payload admin
|
||||
- Images need to be re-uploaded to Payload Media
|
||||
EOF
|
||||
|
||||
log_success "Migration report: $TARGET_PATH/MIGRATION_REPORT.md"
|
||||
}
|
||||
|
||||
main() {
|
||||
echo "=============================================="
|
||||
echo " Astro → Payload CMS Migration Tool"
|
||||
echo " Convert MDX/MD to Payload CMS with Lexical"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
|
||||
if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then
|
||||
print_usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -z "$SOURCE_PATH" ]; then
|
||||
print_usage
|
||||
echo ""
|
||||
log_error "Please specify source path"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$SOURCE_PATH" ]; then
|
||||
log_error "Source path not found: $SOURCE_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$TARGET_PATH" ]; then
|
||||
log_error "Target path not found: $TARGET_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
detect_content_type
|
||||
backup_content
|
||||
analyze_content
|
||||
create_lexical_content
|
||||
create_payload_import_script
|
||||
create_migration_report
|
||||
|
||||
echo ""
|
||||
echo "=============================================="
|
||||
log_success "Migration preparation complete!"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. cd $TARGET_PATH"
|
||||
echo " 2. Review converted content in src/content-migration/"
|
||||
echo " 3. Run: npm run dev"
|
||||
echo " 4. Import: npx tsx scripts/import-content.ts"
|
||||
echo " 5. Verify in Payload admin (http://localhost:3002/admin)"
|
||||
echo ""
|
||||
}
|
||||
|
||||
main "$@"
|
||||
327
skills/website-creator/scripts/install-tina-backend.sh
Executable file
327
skills/website-creator/scripts/install-tina-backend.sh
Executable file
@@ -0,0 +1,327 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
BACKEND_PATH="${1:-./tina-backend}"
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
print_usage() {
|
||||
cat << EOF
|
||||
Usage: $(basename "$0") [target-path]
|
||||
|
||||
Install Tina CMS Backend (self-hosted)
|
||||
|
||||
Arguments:
|
||||
target-path Path where Tina backend will be installed (default: ./tina-backend)
|
||||
|
||||
Examples:
|
||||
$(basename "$0") /opt/tina-backend
|
||||
|
||||
This script installs a self-hosted Tina CMS backend with:
|
||||
- Auth.js authentication
|
||||
- SQLite database adapter
|
||||
- Git provider for content
|
||||
- Next.js API routes
|
||||
|
||||
Requirements:
|
||||
- Node.js 18+
|
||||
- npm or yarn
|
||||
- git
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
main() {
|
||||
echo "=============================================="
|
||||
echo " Tina CMS Backend Installer (Self-Hosted)"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
|
||||
if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then
|
||||
print_usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -d "$BACKEND_PATH" ]; then
|
||||
log_error "Directory already exists: $BACKEND_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Creating Tina backend at: $BACKEND_PATH"
|
||||
mkdir -p "$BACKEND_PATH"
|
||||
cd "$BACKEND_PATH"
|
||||
|
||||
log_info "Creating package.json..."
|
||||
cat > package.json << 'PKGEOF'
|
||||
{
|
||||
"name": "tina-backend",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "tinacms dev",
|
||||
"build": "tinacms build",
|
||||
"start": "tinacms start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/core": "^0.34.0",
|
||||
"@auth/drizzle-adapter": "^1.4.0",
|
||||
"@libsql/client": "^0.14.0",
|
||||
"@tinacms/auth": "^2.0.0",
|
||||
"@tinacms/database": "^2.0.0",
|
||||
"@tinacms/git-provider": "^2.0.0",
|
||||
"@tinacms/graphql": "^2.0.0",
|
||||
"@tinacms/mssql": "^2.0.0",
|
||||
"@tinacms/server": "^2.0.0",
|
||||
"drizzle-orm": "^0.38.0",
|
||||
"next": "^14.0.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"tinacms": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"typescript": "^5.0.0"
|
||||
}
|
||||
}
|
||||
PKGEOF
|
||||
|
||||
log_info "Creating TypeScript config..."
|
||||
cat > tsconfig.json << 'TSEOF'
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src/**/*", "tina/**/*"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
TSEOF
|
||||
|
||||
mkdir -p src/app/api/auth/\[...nextauth\]
|
||||
mkdir -p src/app/api/tina/\[\[...tina\]\]
|
||||
mkdir -p tina
|
||||
|
||||
log_info "Creating Auth.js configuration..."
|
||||
cat > src/auth.config.ts << 'AUTHEOF'
|
||||
import { AuthConfig } from "@auth/core/types";
|
||||
|
||||
const authConfig: AuthConfig = {
|
||||
secret: process.env.NEXTAUTH_SECRET || "your-secret-change-in-production",
|
||||
providers: [
|
||||
{
|
||||
id: "github",
|
||||
name: "GitHub",
|
||||
type: "oauth",
|
||||
clientId: process.env.GITHUB_ID || "",
|
||||
clientSecret: process.env.GITHUB_SECRET || "",
|
||||
},
|
||||
],
|
||||
callbacks: {
|
||||
async session({ session, token }) {
|
||||
if (session.user && token.sub) {
|
||||
session.user.email = token.email as string;
|
||||
}
|
||||
return session;
|
||||
},
|
||||
},
|
||||
pages: {
|
||||
signIn: "/auth/signin",
|
||||
},
|
||||
};
|
||||
|
||||
export default authConfig;
|
||||
AUTHEOF
|
||||
|
||||
log_info "Creating NextAuth API route..."
|
||||
cat > 'src/app/api/auth/[...nextauth]/route.ts' << 'NEXTAUTHEOF'
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { AuthHandler } from "@auth/core";
|
||||
import authConfig from "../../../auth.config";
|
||||
|
||||
const authHandler = (req: NextRequest) =>
|
||||
AuthHandler({
|
||||
...authConfig,
|
||||
req: req as any,
|
||||
resolve(): Promise<any> {
|
||||
throw new Error("Function not implemented.");
|
||||
},
|
||||
secret: authConfig.secret!,
|
||||
trustHost: true,
|
||||
});
|
||||
|
||||
export { authHandler as GET, authHandler as POST };
|
||||
NEXTAUTHEOF
|
||||
|
||||
log_info "Creating Tina API route..."
|
||||
cat > 'src/app/api/tina/[[...tina]]/route.ts' << 'TINAEOF'
|
||||
import { TinaNodeBackend } from "@tinacms/server";
|
||||
import authConfig from "../../../../auth.config";
|
||||
import { branchName } from "./branch";
|
||||
|
||||
const tinaBackend = TinaNodeBackend({
|
||||
authConfig: authConfig as any,
|
||||
branch: branchName,
|
||||
});
|
||||
|
||||
export { tinaBackend as GET, tinaBackend as POST };
|
||||
TINAEOF
|
||||
|
||||
log_info "Creating Tina branch configuration..."
|
||||
cat > src/app/api/tina/branch.ts << 'BRANCHEMAP'
|
||||
export const branchName = process.env.TINA_BRANCH || "main";
|
||||
BRANCHEMAP
|
||||
|
||||
log_info "Creating database schema..."
|
||||
mkdir -p src/lib
|
||||
cat > src/lib/schema.ts << 'DBEOF'
|
||||
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
|
||||
|
||||
export const users = sqliteTable("users", {
|
||||
id: text("id").primaryKey(),
|
||||
name: text("name"),
|
||||
email: text("email").unique(),
|
||||
emailVerified: integer("email_verified", { mode: "boolean" }),
|
||||
image: text("image"),
|
||||
createdAt: integer("created_at", { mode: "timestamp" }),
|
||||
updatedAt: integer("updated_at", { mode: "timestamp" }),
|
||||
});
|
||||
|
||||
export const accounts = sqliteTable("accounts", {
|
||||
id: text("id").primaryKey(),
|
||||
userId: text("user_id")
|
||||
.references(() => users.id)
|
||||
.notNull(),
|
||||
type: text("type").notNull(),
|
||||
provider: text("provider").notNull(),
|
||||
providerAccountId: text("provider_account_id").notNull(),
|
||||
refresh_token: text("refresh_token"),
|
||||
access_token: text("access_token"),
|
||||
expires_at: integer("expires_at"),
|
||||
token_type: text("token_type"),
|
||||
scope: text("scope"),
|
||||
id_token: text("id_token"),
|
||||
session_state: text("session_state"),
|
||||
});
|
||||
|
||||
export const sessions = sqliteTable("sessions", {
|
||||
id: text("id").primaryKey(),
|
||||
sessionToken: text("session_token").unique(),
|
||||
userId: text("user_id")
|
||||
.references(() => users.id)
|
||||
.notNull(),
|
||||
expires: integer("expires", { mode: "timestamp" }).notNull(),
|
||||
});
|
||||
|
||||
export const verificationTokens = sqliteTable("verification_tokens", {
|
||||
identifier: text("identifier").notNull(),
|
||||
token: text("token").notNull(),
|
||||
expires: integer("expires", { mode: "timestamp" }).notNull(),
|
||||
});
|
||||
DBEOF
|
||||
|
||||
log_info "Creating database client..."
|
||||
cat > src/lib/db.ts << 'DBCEOF'
|
||||
import { createClient } from "@libsql/client";
|
||||
import { drizzle } from "drizzle-orm/libsql";
|
||||
import * as schema from "./schema";
|
||||
|
||||
const client = createClient({
|
||||
url: process.env.DATABASE_URL || "file:local.db",
|
||||
});
|
||||
|
||||
export const db = drizzle(client, { schema });
|
||||
DBCEOF
|
||||
|
||||
log_info "Creating environment template..."
|
||||
cat > .env.example << 'ENVEOF'
|
||||
NEXTAUTH_SECRET=generate-a-random-secret-here
|
||||
NEXTAUTH_URL=http://localhost:3000
|
||||
|
||||
GITHUB_ID=your-github-oauth-app-client-id
|
||||
GITHUB_SECRET=your-github-oauth-app-client-secret
|
||||
|
||||
DATABASE_URL=file:local.db
|
||||
TINA_BRANCH=main
|
||||
ENVEOF
|
||||
|
||||
log_info "Creating README..."
|
||||
cat > README.md << 'READMEEOF'
|
||||
# Tina CMS Backend (Self-Hosted)
|
||||
|
||||
Self-hosted Tina CMS backend with Auth.js authentication and SQLite database.
|
||||
|
||||
## Setup
|
||||
|
||||
1. Install dependencies:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. Configure environment:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Edit .env with your settings
|
||||
```
|
||||
|
||||
3. Set up GitHub OAuth App:
|
||||
- Go to https://github.com/settings/developers
|
||||
- Create a new OAuth App
|
||||
- Set callback URL to: `http://your-domain.com/api/auth/callback/github`
|
||||
|
||||
4. Start development:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| NEXTAUTH_SECRET | Random secret for NextAuth |
|
||||
| NEXTAUTH_URL | Your site URL |
|
||||
| GITHUB_ID | GitHub OAuth Client ID |
|
||||
| GITHUB_SECRET | GitHub OAuth Client Secret |
|
||||
| DATABASE_URL | SQLite database path |
|
||||
| TINA_BRANCH | Git branch for content |
|
||||
|
||||
## Connecting Frontend
|
||||
|
||||
In your Astro frontend's `tina/config.ts`:
|
||||
|
||||
```ts
|
||||
import { defineConfig } from "tinacms";
|
||||
|
||||
export default defineConfig({
|
||||
apiUrl: "https://your-tina-backend.com",
|
||||
contentApiUrl: "https://your-tina-backend.com",
|
||||
});
|
||||
```
|
||||
READMEEOF
|
||||
|
||||
log_success "Tina backend created at: $BACKEND_PATH"
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. cd $BACKEND_PATH"
|
||||
echo " 2. npm install"
|
||||
echo " 3. cp .env.example .env"
|
||||
echo " 4. Configure GitHub OAuth App"
|
||||
echo " 5. npm run dev"
|
||||
echo ""
|
||||
}
|
||||
|
||||
main "$@"
|
||||
443
skills/website-creator/scripts/migrate-tina.sh
Executable file
443
skills/website-creator/scripts/migrate-tina.sh
Executable file
@@ -0,0 +1,443 @@
|
||||
#!/usr/bin/env bash
|
||||
#===============================================================================
|
||||
# migrate-tina.sh - Migrate existing websites to Astro + Tina CMS
|
||||
#
|
||||
# Usage: ./migrate-tina.sh [source-path] [target-path]
|
||||
#
|
||||
# This script migrates websites to Astro + Tina CMS:
|
||||
# - Converts content to Tina CMS format
|
||||
# - Sets up Astro DB for consent logging
|
||||
# - Adds PDPA-compliant consent system
|
||||
# - Preserves content and structure
|
||||
#
|
||||
# Requirements:
|
||||
# - node.js 20+
|
||||
# - npm
|
||||
# - git
|
||||
#
|
||||
#===============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
SOURCE_PATH="${1:-}"
|
||||
TARGET_PATH="${2:-.}"
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
print_usage() {
|
||||
cat << EOF
|
||||
Usage: $(basename "$0") [source-path] [target-path]
|
||||
|
||||
Migrate existing website to Astro + Tina CMS
|
||||
|
||||
Arguments:
|
||||
source-path Path to existing website project
|
||||
target-path Path for the migrated Astro + Tina project
|
||||
|
||||
Examples:
|
||||
$(basename "$0") /path/to/existing-site /path/to/migrated-site
|
||||
|
||||
Features:
|
||||
- Detects source website technology (Astro, Next.js, etc.)
|
||||
- Converts content to Tina CMS format
|
||||
- Sets up Astro DB for consent logging (PDPA compliant)
|
||||
- Adds cookie consent banner with Thai law compliance
|
||||
- Preserves SEO metadata and content structure
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
detect_source_type() {
|
||||
log_info "Detecting source website type..."
|
||||
|
||||
cd "$SOURCE_PATH"
|
||||
|
||||
if [ -f "astro.config.mjs" ] || [ -f "astro.config.ts" ]; then
|
||||
SOURCE_TYPE="astro"
|
||||
log_success "Detected: Astro"
|
||||
elif [ -f "next.config.js" ] || [ -f "next.config.mjs" ] || [ -f "package.json" ] && grep -q "next" package.json 2>/dev/null; then
|
||||
SOURCE_TYPE="nextjs"
|
||||
log_success "Detected: Next.js"
|
||||
elif [ -f "package.json" ] && grep -q "remix" package.json 2>/dev/null; then
|
||||
SOURCE_TYPE="remix"
|
||||
log_success "Detected: Remix"
|
||||
elif [ -d "src/content" ] || [ -d "content/posts" ]; then
|
||||
SOURCE_TYPE="generic"
|
||||
log_success "Detected: Generic static site"
|
||||
else
|
||||
log_warning "Could not detect source type, assuming generic"
|
||||
SOURCE_TYPE="generic"
|
||||
fi
|
||||
}
|
||||
|
||||
analyze_source_content() {
|
||||
log_info "Analyzing source content..."
|
||||
|
||||
cd "$SOURCE_PATH"
|
||||
|
||||
local md_count=$(find . -type f \( -name "*.md" -o -name "*.mdx" \) 2>/dev/null | grep -v node_modules | wc -l)
|
||||
local astro_count=$(find . -type f -name "*.astro" 2>/dev/null | grep -v node_modules | wc -l)
|
||||
local pages_count=$(find . -type f \( -name "*.tsx" -o -name "*.jsx" \) 2>/dev/null | grep -v node_modules | grep -E "pages/|app/" | wc -l)
|
||||
|
||||
echo ""
|
||||
echo " Analysis Results:"
|
||||
echo " ─────────────────"
|
||||
echo " Markdown/MDX files: $md_count"
|
||||
echo " Astro components: $astro_count"
|
||||
echo " Pages (tsx/jsx): $pages_count"
|
||||
echo ""
|
||||
|
||||
# List sample content files
|
||||
if [ $md_count -gt 0 ]; then
|
||||
echo " Sample content files:"
|
||||
find . -type f \( -name "*.md" -o -name "*.mdx" \) 2>/dev/null | grep -v node_modules | head -5 | while read -r f; do
|
||||
echo " - $f"
|
||||
done
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
copy_template() {
|
||||
log_info "Copying Astro+Tina template..."
|
||||
|
||||
local template_dir="$(dirname "$(dirname "$(readlink -f "$0")")")/templates/astro-tina-starter"
|
||||
|
||||
if [ ! -d "$template_dir" ]; then
|
||||
log_error "Template not found: $template_dir"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cp -r "$template_dir"/* "$TARGET_PATH/"
|
||||
cp -r "$template_dir"/.* "$TARGET_PATH/" 2>/dev/null || true
|
||||
|
||||
log_success "Template copied to: $TARGET_PATH"
|
||||
}
|
||||
|
||||
migrate_content() {
|
||||
log_info "Migrating content to Tina format..."
|
||||
|
||||
cd "$SOURCE_PATH"
|
||||
|
||||
# Detect content directory
|
||||
local content_dir=""
|
||||
if [ -d "src/content" ]; then
|
||||
content_dir="src/content"
|
||||
elif [ -d "content" ]; then
|
||||
content_dir="content"
|
||||
elif [ -d "content/posts" ]; then
|
||||
content_dir="content/posts"
|
||||
fi
|
||||
|
||||
if [ -z "$content_dir" ]; then
|
||||
log_warning "No content directory found, creating default structure"
|
||||
mkdir -p "$TARGET_PATH/src/content"
|
||||
return
|
||||
fi
|
||||
|
||||
# Create Tina content directory
|
||||
mkdir -p "$TARGET_PATH/src/content"
|
||||
|
||||
# Copy markdown/mdx files
|
||||
find "$content_dir" -type f \( -name "*.md" -o -name "*.mdx" \) 2>/dev/null | while read -r file; do
|
||||
local relative_path="${file#$SOURCE_PATH/$content_dir/}"
|
||||
local target_file="$TARGET_PATH/src/content/$relative_path"
|
||||
|
||||
mkdir -p "$(dirname "$target_file")"
|
||||
cp "$file" "$target_file"
|
||||
|
||||
echo " Migrated: $relative_path"
|
||||
done
|
||||
|
||||
log_success "Content migration complete"
|
||||
}
|
||||
|
||||
add_consent_system() {
|
||||
log_info "Adding PDPA-compliant consent system..."
|
||||
|
||||
local consent_template="$(dirname "$(dirname "$(readlink -f "$0")")")/templates/consent"
|
||||
|
||||
if [ ! -d "$consent_template" ]; then
|
||||
log_warning "Consent template not found, skipping"
|
||||
return
|
||||
fi
|
||||
|
||||
# Copy consent files
|
||||
cp -r "$consent_template"/* "$TARGET_PATH/src/components/consent/" 2>/dev/null || true
|
||||
|
||||
log_success "Consent system added"
|
||||
}
|
||||
|
||||
create_tina_schema() {
|
||||
log_info "Creating Tina CMS schema..."
|
||||
|
||||
cd "$TARGET_PATH"
|
||||
|
||||
# Ensure .tina directory exists
|
||||
mkdir -p .tina
|
||||
|
||||
# Create or update schema
|
||||
cat > .tina/schema.ts << 'EOF'
|
||||
import { defineSchema, config } from 'tinacms'
|
||||
|
||||
// Your content collections
|
||||
const schema = defineSchema({
|
||||
collections: [
|
||||
{
|
||||
name: 'post',
|
||||
label: 'Posts',
|
||||
path: 'src/content/posts',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'slug',
|
||||
label: 'Slug',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'datetime',
|
||||
name: 'date',
|
||||
label: 'Date',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'author',
|
||||
label: 'Author',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'image',
|
||||
label: 'Featured Image',
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'description',
|
||||
label: 'Description',
|
||||
},
|
||||
{
|
||||
type: 'rich-text',
|
||||
name: 'body',
|
||||
label: 'Body',
|
||||
isBody: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'page',
|
||||
label: 'Pages',
|
||||
path: 'src/content/pages',
|
||||
fields: [
|
||||
{
|
||||
type: 'string',
|
||||
name: 'title',
|
||||
label: 'Title',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'string',
|
||||
name: 'slug',
|
||||
label: 'Slug',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
type: 'rich-text',
|
||||
name: 'body',
|
||||
label: 'Body',
|
||||
isBody: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
export default config({
|
||||
schema,
|
||||
// Other config options
|
||||
})
|
||||
EOF
|
||||
|
||||
log_success "Tina schema created"
|
||||
}
|
||||
|
||||
create_migration_report() {
|
||||
log_info "Creating migration report..."
|
||||
|
||||
cd "$SOURCE_PATH"
|
||||
|
||||
local md_count=$(find . -type f \( -name "*.md" -o -name "*.mdx" \) 2>/dev/null | grep -v node_modules | wc -l)
|
||||
|
||||
cat > "$TARGET_PATH/MIGRATION_REPORT.md" << EOF
|
||||
# Migration Report: → Astro + Tina CMS
|
||||
|
||||
## Source
|
||||
- **Original Type:** $SOURCE_TYPE
|
||||
- **Path:** $SOURCE_PATH
|
||||
- **Date:** $(date)
|
||||
|
||||
## Statistics
|
||||
- **Content Files Migrated:** $md_count
|
||||
|
||||
## What's Included
|
||||
|
||||
### ✅ Astro 6.1.7
|
||||
Modern static site framework with excellent performance.
|
||||
|
||||
### ✅ Tina CMS
|
||||
Self-hosted Git-based CMS for visual content editing.
|
||||
|
||||
### ✅ Tailwind CSS 4.x
|
||||
Latest Tailwind with @tailwindcss/vite plugin.
|
||||
|
||||
### ✅ Astro DB
|
||||
Built-in database for consent logging and dynamic content.
|
||||
|
||||
### ✅ PDPA Consent System
|
||||
Thai Personal Data Protection Act compliant cookie consent:
|
||||
- Cookie banner with Accept/Reject/Preferences
|
||||
- Consent logging in Astro DB
|
||||
- API endpoint for consent management
|
||||
|
||||
### ✅ Nano Stores
|
||||
Lightweight client-side state management.
|
||||
|
||||
## Project Structure
|
||||
|
||||
\`\`\`
|
||||
$TARGET_PATH/
|
||||
├── src/
|
||||
│ ├── components/
|
||||
│ │ └── consent/ # PDPA consent system
|
||||
│ ├── content/
|
||||
│ │ ├── posts/ # Blog posts (Tina managed)
|
||||
│ │ └── pages/ # Static pages (Tina managed)
|
||||
│ ├── layouts/
|
||||
│ │ └── Layout.astro
|
||||
│ ├── pages/
|
||||
│ │ └── index.astro
|
||||
│ └── styles/
|
||||
│ └── global.css
|
||||
├── .tina/
|
||||
│ └── schema.ts # Tina content schema
|
||||
├── db/
|
||||
│ └── config.ts # Astro DB config
|
||||
├── Dockerfile
|
||||
└── AGENTS.md # AI agent instructions
|
||||
\`\`\`
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Install dependencies:**
|
||||
\`\`\`bash
|
||||
cd $TARGET_PATH
|
||||
npm install
|
||||
\`\`\`
|
||||
|
||||
2. **Set up environment:**
|
||||
\`\`\`bash
|
||||
cp .env.example .env
|
||||
# Edit .env with your settings
|
||||
\`\`\`
|
||||
|
||||
3. **Start development:**
|
||||
\`\`\`bash
|
||||
npm run dev
|
||||
\`\`\`
|
||||
|
||||
4. **Access Tina Admin:**
|
||||
- Visit \`http://localhost:4321/admin\` (when in dev mode)
|
||||
- Or \`http://localhost:4321/___tina\` for direct access
|
||||
|
||||
5. **Configure Tina Backend** (for production):
|
||||
\`\`\`bash
|
||||
./scripts/install-tina-backend.sh
|
||||
\`\`\`
|
||||
|
||||
## Tina CMS Setup
|
||||
|
||||
For production, you'll need to set up the Tina backend:
|
||||
\`\`\`bash
|
||||
./scripts/install-tina-backend.sh
|
||||
\`\`\`
|
||||
|
||||
This will install:
|
||||
- Auth.js for authentication
|
||||
- Database adapter for content storage
|
||||
- Git provider for content management
|
||||
|
||||
## PDPA Compliance
|
||||
|
||||
The consent system logs:
|
||||
- User consent choices (accept/reject)
|
||||
- Cookie categories (analytics, marketing, functional)
|
||||
- Timestamp and user agent
|
||||
- IP address (for compliance auditing)
|
||||
|
||||
Logs are stored in Astro DB and can be exported for compliance reporting.
|
||||
EOF
|
||||
|
||||
log_success "Migration report: $TARGET_PATH/MIGRATION_REPORT.md"
|
||||
}
|
||||
|
||||
main() {
|
||||
echo "=============================================="
|
||||
echo " Website → Astro + Tina CMS Migration Tool"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
|
||||
if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then
|
||||
print_usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ -z "$SOURCE_PATH" ]; then
|
||||
print_usage
|
||||
echo ""
|
||||
log_error "Please specify source path"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$SOURCE_PATH" ]; then
|
||||
log_error "Source path not found: $SOURCE_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ ! -d "$TARGET_PATH" ]; then
|
||||
mkdir -p "$TARGET_PATH"
|
||||
fi
|
||||
|
||||
detect_source_type
|
||||
analyze_source_content
|
||||
copy_template
|
||||
migrate_content
|
||||
add_consent_system
|
||||
create_tina_schema
|
||||
create_migration_report
|
||||
|
||||
echo ""
|
||||
echo "=============================================="
|
||||
log_success "Migration complete!"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo " 1. cd $TARGET_PATH"
|
||||
echo " 2. npm install"
|
||||
echo " 3. npm run dev"
|
||||
echo " 4. See MIGRATION_REPORT.md for details"
|
||||
echo ""
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -1,119 +1,79 @@
|
||||
#!/usr/bin/env bash
|
||||
#===============================================================================
|
||||
# new-project.sh - สร้าง Next.js + Payload CMS project ใหม่จาก Template
|
||||
#
|
||||
# Usage: ./new-project.sh [project-name] [project-path]
|
||||
#
|
||||
# สร้าง Next.js + Payload CMS project ใหม่โดย:
|
||||
# 1. คัดลอก nextjs-payload-starter template
|
||||
# 2. ติดตั้ง dependencies
|
||||
# 3. ตั้งค่า environment
|
||||
#
|
||||
# Requirements:
|
||||
# - git
|
||||
# - node.js 20+
|
||||
# - npm
|
||||
#
|
||||
#===============================================================================
|
||||
|
||||
set -e
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
NC='\033[0m'
|
||||
|
||||
# Default values
|
||||
PROJECT_NAME="${1:-}"
|
||||
PROJECT_PATH="${2:-.}"
|
||||
|
||||
# Get skill directory
|
||||
SKILL_DIR="$(dirname "$(dirname "$(readlink -f "$0")")")"
|
||||
TEMPLATE_DIR="$SKILL_DIR/templates/nextjs-payload-starter"
|
||||
TEMPLATE_DIR="$SKILL_DIR/templates/astro-tina-starter"
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Helper functions
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
|
||||
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
print_usage() {
|
||||
cat << EOF
|
||||
Usage: $(basename "$0") [project-name] [project-path]
|
||||
|
||||
สร้าง Next.js + Payload CMS project ใหม่จาก Template
|
||||
Create new Astro + Tina CMS project from template
|
||||
|
||||
Arguments:
|
||||
project-name ชื่อ project (optional)
|
||||
project-path ที่อยู่ project (default: current directory)
|
||||
project-name Project name (optional)
|
||||
project-path Project location (default: current directory)
|
||||
|
||||
Examples:
|
||||
$(basename "$0") my-website
|
||||
$(basename "$0") my-website /path/to/projects/
|
||||
|
||||
Creates:
|
||||
- Astro 6.1.7 framework
|
||||
- Tailwind CSS 4.x
|
||||
- Tina CMS (self-hosted)
|
||||
- Astro DB for consent logging
|
||||
- PDPA-compliant consent system
|
||||
|
||||
EOF
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Pre-flight checks
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
check_requirements() {
|
||||
log_info "ตรวจสอบความต้องการของระบบ..."
|
||||
log_info "Checking requirements..."
|
||||
|
||||
# Check git
|
||||
if ! command -v git &> /dev/null; then
|
||||
log_error "git ไม่พบ กรุณาติดตั้ง git ก่อน"
|
||||
log_error "git not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check node
|
||||
if ! command -v node &> /dev/null; then
|
||||
log_error "node.js ไม่พบ กรุณาติดตั้ง node.js ก่อน"
|
||||
log_error "node.js not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
NODE_VERSION=$(node -v | cut -d'v' -f2 | cut -d'.' -f1)
|
||||
if [ "$NODE_VERSION" -lt 20 ]; then
|
||||
log_error "node.js version ต้อง >= 20 (ตอนนี้: $(node -v))"
|
||||
log_error "node.js >= 20 required (current: $(node -v))"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check npm
|
||||
if ! command -v npm &> /dev/null; then
|
||||
log_error "npm ไม่พบ กรุณาติดตั้ง npm ก่อน"
|
||||
log_error "npm not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check template exists
|
||||
if [ ! -d "$TEMPLATE_DIR" ]; then
|
||||
log_error "ไม่พบ Next.js Payload Starter Template: $TEMPLATE_DIR"
|
||||
log_error "Template not found: $TEMPLATE_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "ความต้องการของระบบผ่าน (git, node $(node -v), npm)"
|
||||
log_success "Requirements OK (git, node $(node -v), npm)"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Create project directory
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
setup_directory() {
|
||||
local actual_project_path="$PROJECT_PATH"
|
||||
|
||||
@@ -121,13 +81,11 @@ setup_directory() {
|
||||
actual_project_path="$PROJECT_PATH/$PROJECT_NAME"
|
||||
fi
|
||||
|
||||
# Create directory
|
||||
mkdir -p "$actual_project_path"
|
||||
|
||||
# Check if directory is empty
|
||||
if [ "$(ls -A "$actual_project_path" | wc -l)" -gt 0 ]; then
|
||||
log_warning "Directory ไม่ว่าง: $actual_project_path"
|
||||
read -p "ดำเนินต่อ? (y/n): " -n 1 -r
|
||||
log_warning "Directory not empty: $actual_project_path"
|
||||
read -p "Continue? (y/n): " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
exit 1
|
||||
@@ -138,32 +96,37 @@ setup_directory() {
|
||||
log_info "Project path: $PROJECT_PATH"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Copy template
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
copy_template() {
|
||||
log_info "คัดลอก Next.js Payload Starter Template..."
|
||||
log_info "Copying Astro+Tina template..."
|
||||
|
||||
# Copy all template files
|
||||
cp -r "$TEMPLATE_DIR/"* "$PROJECT_PATH/"
|
||||
cp -r "$TEMPLATE_DIR/src/collections/access" "$PROJECT_PATH/src/collections/" 2>/dev/null || true
|
||||
cp -r "$TEMPLATE_DIR"/.* "$PROJECT_PATH/" 2>/dev/null || true
|
||||
|
||||
# Copy consent API if exists
|
||||
if [ -d "$SKILL_DIR/templates/consent/api" ]; then
|
||||
mkdir -p "$PROJECT_PATH/src/pages/api"
|
||||
cp "$SKILL_DIR/templates/consent/api/"* "$PROJECT_PATH/src/pages/api/" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
log_success "คัดลอก template เสร็จสมบูรณ์"
|
||||
log_success "Template copied"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Copy legal templates
|
||||
#-------------------------------------------------------------------------------
|
||||
copy_consent_system() {
|
||||
log_info "Adding PDPA consent system..."
|
||||
|
||||
local consent_template="$SKILL_DIR/templates/consent"
|
||||
|
||||
if [ -d "$consent_template" ]; then
|
||||
mkdir -p "$PROJECT_PATH/src/components/consent"
|
||||
cp "$consent_template/ConsentBanner.astro" "$PROJECT_PATH/src/components/consent/" 2>/dev/null || true
|
||||
cp "$consent_template/stores/"* "$PROJECT_PATH/src/stores/" 2>/dev/null || true
|
||||
|
||||
mkdir -p "$PROJECT_PATH/src/pages/api"
|
||||
cp "$consent_template/api/consent.ts" "$PROJECT_PATH/src/pages/api/" 2>/dev/null || true
|
||||
|
||||
mkdir -p "$PROJECT_PATH/db"
|
||||
cp "$consent_template/db/config.ts" "$PROJECT_PATH/db/" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
log_success "Consent system added"
|
||||
}
|
||||
|
||||
copy_legal_templates() {
|
||||
log_info "คัดลอก PDPA templates..."
|
||||
log_info "Copying PDPA legal templates..."
|
||||
|
||||
mkdir -p "$PROJECT_PATH/src/content/pages"
|
||||
|
||||
@@ -175,168 +138,92 @@ copy_legal_templates() {
|
||||
cp "$SKILL_DIR/templates/terms-of-service.md" "$PROJECT_PATH/src/content/pages/"
|
||||
fi
|
||||
|
||||
log_success "คัดลอก PDPA templates เสร็จสมบูรณ์"
|
||||
log_success "Legal templates copied"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Install dependencies
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
install_dependencies() {
|
||||
log_info "ติดตั้ง dependencies..."
|
||||
log_info "Installing dependencies..."
|
||||
|
||||
cd "$PROJECT_PATH"
|
||||
npm install
|
||||
|
||||
log_success "ติดตั้ง dependencies เสร็จสมบูรณ์"
|
||||
log_success "Dependencies installed"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Setup environment
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
setup_environment() {
|
||||
log_info "ตั้งค่า environment..."
|
||||
log_info "Setting up environment..."
|
||||
|
||||
cd "$PROJECT_PATH"
|
||||
|
||||
if [ ! -f ".env" ]; then
|
||||
if [ -f ".env.example" ]; then
|
||||
cp .env.example .env
|
||||
log_success "สร้าง .env จาก .env.example"
|
||||
log_warning "กรุณาแก้ไข .env และใส่ DATABASE_URL ที่ถูกต้อง"
|
||||
log_success "Created .env from .env.example"
|
||||
else
|
||||
cat > .env << 'EOF'
|
||||
# Payload CMS
|
||||
PAYLOAD_SECRET=change-this-secret-key-at-least-32-characters
|
||||
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
|
||||
|
||||
# Server
|
||||
SERVER_URL=http://localhost:4321
|
||||
NODE_ENV=development
|
||||
PUBLIC_SITE_URL=http://localhost:4321
|
||||
TINA_TOKEN=your-tina-token
|
||||
EOF
|
||||
log_success "สร้าง .env เริ่มต้น"
|
||||
log_warning "กรุณาแก้ไข .env และใส่ DATABASE_URL ที่ถูกต้อง"
|
||||
log_success "Created default .env"
|
||||
fi
|
||||
else
|
||||
log_info ".env มีอยู่แล้ว"
|
||||
fi
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Create AI_RULES.md
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
create_ai_rules() {
|
||||
log_info "สร้าง AI_RULES.md..."
|
||||
|
||||
cd "$PROJECT_PATH"
|
||||
|
||||
cat > AI_RULES.md << 'EOF'
|
||||
# AI Rules
|
||||
|
||||
## Tech Stack Overview
|
||||
|
||||
- **Frontend:** Next.js App Router + TypeScript
|
||||
- **Backend/CMS:** Payload CMS 3.0
|
||||
- **Database:** MongoDB (via mongooseAdapter)
|
||||
- **Styling:** Tailwind CSS v4
|
||||
- **Authentication:** Payload built-in auth with role-based access
|
||||
- **Image Handling:** Payload Media collection
|
||||
|
||||
## File Organization
|
||||
|
||||
- **Collections:** Define Payload collections in `src/collections/`
|
||||
- **Pages:** Next.js App Router in `src/app/`
|
||||
- **Components:** Reusable components in `src/components/`
|
||||
- **Styles:** Global styles in `src/app/globals.css`
|
||||
|
||||
## Never Modify These Files
|
||||
|
||||
- `src/payload-types.ts` - Auto-generated by Payload
|
||||
- `src/migrations/` - Database migration files
|
||||
|
||||
## Thai-First
|
||||
|
||||
- ใช้ Kanit หรือ Noto Sans Thai fonts
|
||||
- Thai typography CSS
|
||||
- Thai structured data (LocalBusiness, Organization)
|
||||
- ภาษาไทยเป็นหลักใน content
|
||||
EOF
|
||||
|
||||
log_success "สร้าง AI_RULES.md เสร็จสมบูรณ์"
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Initialize git
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
init_git() {
|
||||
log_info "เริ่มต้น git..."
|
||||
log_info "Initializing git..."
|
||||
|
||||
cd "$PROJECT_PATH"
|
||||
|
||||
if [ ! -d ".git" ]; then
|
||||
git init
|
||||
git add .
|
||||
git commit -m "Initial commit: Next.js + Payload CMS starter"
|
||||
log_success "เริ่มต้น git เสร็จสมบูรณ์"
|
||||
else
|
||||
log_info "git repo มีอยู่แล้ว"
|
||||
git commit -m "Initial commit: Astro + Tina CMS starter"
|
||||
log_success "Git initialized"
|
||||
fi
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Show project structure
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
show_structure() {
|
||||
log_info "โครงสร้าง project:"
|
||||
log_info "Project structure:"
|
||||
cd "$PROJECT_PATH"
|
||||
echo ""
|
||||
find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.astro" -o -name "*.mjs" -o -name "*.css" -o -name "*.md" -o -name "package.json" \) 2>/dev/null | grep -v node_modules | sort | head -30
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Main
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
main() {
|
||||
echo "=============================================="
|
||||
echo " Next.js + Payload CMS Project Creator"
|
||||
echo " Using Next.js Payload Starter"
|
||||
echo " Astro + Tina CMS Project Creator"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
|
||||
# Parse arguments
|
||||
if [ "$1" == "-h" ] || [ "$1" == "--help" ]; then
|
||||
print_usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Run steps
|
||||
check_requirements
|
||||
setup_directory
|
||||
copy_template
|
||||
copy_consent_system
|
||||
copy_legal_templates
|
||||
install_dependencies
|
||||
setup_environment
|
||||
create_ai_rules
|
||||
init_git
|
||||
show_structure
|
||||
|
||||
echo ""
|
||||
echo "=============================================="
|
||||
log_success "สร้าง Next.js + Payload CMS project เสร็จสมบูรณ์!"
|
||||
log_success "Project created successfully!"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
echo "ขั้นตอนถัดไป:"
|
||||
echo "Next steps:"
|
||||
echo " 1. cd $PROJECT_PATH"
|
||||
echo " 2. แก้ไข .env (MONGODB_URL, PAYLOAD_SECRET)"
|
||||
echo " 3. npm install"
|
||||
echo " 4. npm run dev"
|
||||
echo " 5. เปิด http://localhost:3002/admin สำหรับ Payload admin"
|
||||
echo " 2. npm run dev"
|
||||
echo " 3. Open http://localhost:4321"
|
||||
echo ""
|
||||
echo "For Tina CMS admin:"
|
||||
echo " - npm run dev"
|
||||
echo " - Visit http://localhost:4321/admin"
|
||||
echo ""
|
||||
}
|
||||
|
||||
main "$@"
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user