Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 201 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
name: CI/CD Pipeline

on:
push:
branches:
- main
- dev
pull_request:
types: [opened, synchronize, reopened, closed]

concurrency:
group: "CI-${{ github.ref_name }}-${{ github.event.number || github.sha }}"
cancel-in-progress: true

permissions:
contents: read
pull-requests: write
issues: write

jobs:
# Build job for all scenarios
build:
runs-on: [self-hosted, core]
if: github.event.action != 'closed'
outputs:
image-tag: ${{ steps.image-tag.outputs.tag }}
port: ${{ steps.port.outputs.port }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Determine image tag and port
id: image-tag
run: |
if [ "${{ github.ref_name }}" = "main" ]; then
echo "tag=latest" >> $GITHUB_OUTPUT
elif [ "${{ github.ref_name }}" = "dev" ]; then
echo "tag=dev" >> $GITHUB_OUTPUT
else
echo "tag=pr-${{ github.event.number }}" >> $GITHUB_OUTPUT
fi

- name: Determine port for PR
id: port
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
# Calculate port: 3100 + PR number (ensures unique ports)
PORT=$((3100 + ${{ github.event.number }}))
echo "port=$PORT" >> $GITHUB_OUTPUT
fi

- name: Create .env file
run: |
echo "NEXT_PUBLIC_BASE_URL=${{ secrets.NEXT_PUBLIC_BASE_URL }}" >> .env
echo "NEXT_PUBLIC_BASE_URL_ATTACHMENT=${{ secrets.NEXT_PUBLIC_BASE_URL_ATTACHMENT }}" >> .env

- name: Login to Docker Hub
run: docker login -u mrbadri -p ${{ secrets.DOCKER_TOKEN }}

- name: Build Docker Image
run: |
TAG=${{ steps.image-tag.outputs.tag }}
if [ "${{ github.ref_name }}" = "main" ]; then
docker compose -f docker-compose.prod.yml build
elif [ "${{ github.ref_name }}" = "dev" ]; then
docker compose -f docker-compose.dev.yml build
docker tag mrbadri/pixel-client:dev mrbadri/pixel-client:$TAG
else
# For PR - build and tag
docker build -t mrbadri/pixel-client:$TAG -f ./apps/core/Dockerfile .
fi

- name: Push Docker Image to Docker Hub
run: |
TAG=${{ steps.image-tag.outputs.tag }}
docker push mrbadri/pixel-client:$TAG

# Deploy to production (main branch)
deploy-production:
runs-on: [self-hosted, core]
needs: build
if: github.ref_name == 'main' && github.event_name == 'push'
steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Create .env file
run: |
echo "NEXT_PUBLIC_BASE_URL=${{ secrets.NEXT_PUBLIC_BASE_URL }}" >> .env
echo "NEXT_PUBLIC_BASE_URL_ATTACHMENT=${{ secrets.NEXT_PUBLIC_BASE_URL_ATTACHMENT }}" >> .env

- name: Deploy Production
run: |
docker compose -f docker-compose.prod.yml pull client
docker compose -f docker-compose.prod.yml down || true
docker compose -f docker-compose.prod.yml up -d

# Deploy to development (dev branch)
deploy-development:
runs-on: [self-hosted, core]
needs: build
if: github.ref_name == 'dev' && github.event_name == 'push'
steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Create .env file
run: |
echo "NEXT_PUBLIC_BASE_URL=${{ secrets.NEXT_PUBLIC_BASE_URL }}" >> .env
echo "NEXT_PUBLIC_BASE_URL_ATTACHMENT=${{ secrets.NEXT_PUBLIC_BASE_URL_ATTACHMENT }}" >> .env

- name: Deploy Development
run: |
docker compose -f docker-compose.dev.yml pull client-dev
docker compose -f docker-compose.dev.yml down || true
docker compose -f docker-compose.dev.yml up -d

# Deploy PR preview
deploy-preview:
runs-on: [self-hosted, core]
needs: build
if: github.event_name == 'pull_request' && github.event.action != 'closed'
steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Create .env file
run: |
echo "NEXT_PUBLIC_BASE_URL=${{ secrets.NEXT_PUBLIC_BASE_URL }}" >> .env
echo "NEXT_PUBLIC_BASE_URL_ATTACHMENT=${{ secrets.NEXT_PUBLIC_BASE_URL_ATTACHMENT }}" >> .env

- name: Create PR-specific compose file
run: |
PR_NUMBER=${{ github.event.number }}
PORT=${{ needs.build.outputs.port }}

# Create PR-specific docker-compose file
sed "s/PR_NUMBER/$PR_NUMBER/g; s/PORT_NUMBER/$PORT/g" docker-compose.preview.yml > docker-compose.pr-$PR_NUMBER.yml

- name: Deploy Preview
run: |
PR_NUMBER=${{ github.event.number }}

# Pull the image
docker pull mrbadri/pixel-client:pr-$PR_NUMBER

# Stop and remove existing preview if exists
docker compose -f docker-compose.pr-$PR_NUMBER.yml down || true

# Start the preview
docker compose -f docker-compose.pr-$PR_NUMBER.yml up -d

- name: Comment PR with preview link
uses: marocchino/sticky-pull-request-comment@v2
with:
header: preview-deployment
message: |
### 🚀 Preview Deployed Successfully!

Your pull request preview is now available:
- 📍 **Preview URL**: http://82.115.24.87:${{ needs.build.outputs.port }}
- 🔗 **Container**: `client-preview-${{ github.event.number }}`
- 🐳 **Image**: `mrbadri/pixel-client:pr-${{ github.event.number }}`

This preview will be automatically cleaned up when the PR is closed.

# Cleanup PR preview when closed
cleanup-preview:
runs-on: [self-hosted, core]
if: github.event_name == 'pull_request' && github.event.action == 'closed'
steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Cleanup PR preview
run: |
PR_NUMBER=${{ github.event.number }}

# Stop and remove preview containers
docker compose -f docker-compose.pr-$PR_NUMBER.yml down || true

# Remove the compose file
rm -f docker-compose.pr-$PR_NUMBER.yml || true

# Remove the Docker image
docker rmi mrbadri/pixel-client:pr-$PR_NUMBER || true

- name: Post Cleanup Confirmation Comment
if: always()
uses: marocchino/sticky-pull-request-comment@v2
with:
header: preview-cleanup
message: |
### 🧹 Preview Cleanup Complete

All preview deployment resources for this pull request have been successfully removed:
- 🐳 Docker container `client-preview-${{ github.event.number }}` stopped and removed
- 📦 Docker image `mrbadri/pixel-client:pr-${{ github.event.number }}` cleaned up
- 📁 Docker compose file removed

The cleanup process has finished for PR #${{ github.event.number }}.
15 changes: 9 additions & 6 deletions apps/core/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ RUN apk update
RUN apk add --no-cache libc6-compat
# Set working directory
WORKDIR /app
RUN yarn global add turbo
RUN npm install -g pnpm turbo
COPY . .
# RUN npm run prepare:ci
RUN turbo prune core --docker
Expand All @@ -19,13 +19,12 @@ FROM base AS installer
RUN apk update
RUN apk add --no-cache libc6-compat
WORKDIR /app
RUN npm install -g pnpm turbo

# First install the dependencies (as they change less often)
COPY --from=builder /app/out/json/ .
# Configure Yarn with better network settings and use Taobao registry
RUN yarn config set registry https://registry.npmmirror.com/ && \
yarn config set network-timeout 600000 && \
yarn install --network-timeout 600000 --no-network-concurrency
# Configure pnpm with better network settings and use Taobao registry
RUN pnpm install --frozen-lockfile

# Build the project
COPY --from=builder /app/out/full/ .
Expand All @@ -37,7 +36,7 @@ COPY --from=builder /app/out/full/ .
# ARG TURBO_TOKEN
# ENV TURBO_TOKEN=$TURBO_TOKEN

RUN yarn turbo build --filter=core
RUN pnpm turbo build

FROM base AS runner
WORKDIR /app
Expand All @@ -47,6 +46,10 @@ RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
USER nextjs

# Environment variables
ENV NEXT_PUBLIC_BASE_URL=${NEXT_PUBLIC_BASE_URL}
ENV NEXT_PUBLIC_BASE_URL_ATTACHMENT=${NEXT_PUBLIC_BASE_URL_ATTACHMENT}

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=installer --chown=nextjs:nodejs /app/apps/core/.next/standalone ./
Expand Down
19 changes: 19 additions & 0 deletions apps/core/app/(landing)/(home)/[tab]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { LandingPage } from "../../_components/landing-page";

export const revalidate = 60;

export default async function Page({
params,
}: {
params: Promise<{
tab: string;
}>;
searchParams: Promise<{
[key: string]: string | string[] | undefined;
}>;
}) {
const { tab } = await params;

console.log(tab);
return <LandingPage tab={tab} />;
}
21 changes: 21 additions & 0 deletions apps/core/app/(landing)/(home)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { BackgroundLanding } from "../_components/background-landing";
import { TextGenerateEffect } from "../_components/TextGenerateEffect";

const words = `Find thousands of meticulously crafted resources by pixel geniuses to supercharge your creativity.`;

const Layout = ({ children }: { children: React.ReactNode }) => {
return (
<main className="container">
{/* Background */}
<BackgroundLanding />
{/* Header */}
<div className="md:pb-56 md:pt-48 pt-24">
<TextGenerateEffect words={words} />
</div>

{children}
</main>
);
};

export default Layout;
8 changes: 8 additions & 0 deletions apps/core/app/(landing)/(home)/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { LandingPage } from "../_components/landing-page";

// ISR 60s
export const revalidate = 60;

export default function Page() {
return <LandingPage tab="/" />;
}
1 change: 1 addition & 0 deletions apps/core/app/(landing)/_assets/earth.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading