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:
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 "$@"
|
||||
Reference in New Issue
Block a user