Merge pull request #488 from hngprojects/feat/fe-ci/cd #415
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Frontend CI/CD | |
on: | |
push: | |
branches: | |
- dev | |
- main | |
concurrency: | |
group: ${{ github.workflow }}-${{ github.ref }} | |
cancel-in-progress: false | |
jobs: | |
test-and-build: | |
runs-on: aiadgen | |
steps: | |
- name: Checkout Repository | |
uses: actions/checkout@v4 | |
- name: Setup Node.js | |
uses: actions/setup-node@v4 | |
with: | |
node-version: "20.x" | |
- name: Setup pnpm | |
uses: pnpm/action-setup@v3 | |
with: | |
version: latest | |
- name: Create Environment-Specific .env File | |
run: | | |
if [ "${{ github.ref }}" == "refs/heads/main" ]; then | |
echo "NEXT_PUBLIC_API_URL=${{ vars.PROD_API_URL }}" >> .env | |
echo "NEXT_PUBLIC_ENVIRONMENT=production" >> .env | |
elif [ "${{ github.ref }}" == "refs/heads/dev" ]; then | |
echo "NEXT_PUBLIC_API_URL=${{ vars.DEV_API_URL }}" >> .env | |
echo "NEXT_PUBLIC_ENVIRONMENT=test" >> .env | |
fi | |
- name: Install Dependencies | |
run: pnpm install --frozen-lockfile | |
- name: Build Frontend | |
run: pnpm build | |
- name: Upload Build Artifacts | |
uses: actions/upload-artifact@v4 | |
with: | |
name: next-build-${{ github.ref == 'refs/heads/main' && 'prod' || 'dev' }} | |
path: | | |
.next/** | |
!.next/cache/** | |
public/ | |
package.json | |
pnpm-lock.yaml | |
.env | |
if-no-files-found: error | |
include-hidden-files: true | |
compression-level: 6 | |
deploy-production: | |
runs-on: aiadgen | |
needs: [test-and-build] | |
if: github.ref == 'refs/heads/main' | |
steps: | |
- name: Checkout Repository | |
uses: actions/checkout@v4 | |
- name: Download Build Artifacts | |
uses: actions/download-artifact@v4 | |
with: | |
name: next-build-prod | |
path: . | |
- name: Set Environment Variables | |
run: | | |
echo "ENV_NAME=production" >> $GITHUB_ENV | |
echo "DEPLOY_PORT=${{ vars.PROD_DEPLOY_PORT }}" >> $GITHUB_ENV | |
echo "DEPLOY_NAME=${{ vars.PROD_DEPLOY_NAME }}" >> $GITHUB_ENV | |
echo "DEPLOY_DIR=${{ vars.PROD_DEPLOY_DIR }}" >> $GITHUB_ENV | |
echo "SERVER_USER=${{ secrets.PROD_SERVER_USER }}" >> $GITHUB_ENV | |
echo "SERVER_IP=${{ secrets.PROD_SERVER_IP }}" >> $GITHUB_ENV | |
echo "SERVER_PASSWORD=${{ secrets.PROD_SERVER_PASSWORD }}" >> $GITHUB_ENV | |
echo "BRANCH_NAME=main" >> $GITHUB_ENV | |
- name: Send Deployment Start Notification | |
uses: ./.github/actions/slack-notification | |
with: | |
status: 'started' | |
environment: 'PRODUCTION' | |
branch_name: 'main' | |
webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }} | |
- name: Create Deployment Directory if Not Exists | |
run: | | |
sshpass -p "${{ env.SERVER_PASSWORD }}" ssh -o StrictHostKeyChecking=no \ | |
"${{ env.SERVER_USER }}@${{ env.SERVER_IP }}" << EOF | |
if [ ! -d "/home/${{ env.SERVER_USER }}/${{ env.DEPLOY_DIR }}" ]; then | |
echo "Creating directory /home/${{ env.SERVER_USER }}/${{ env.DEPLOY_DIR }}" | |
mkdir -p /home/${{ env.SERVER_USER }}/${{ env.DEPLOY_DIR }} | |
else | |
echo "Directory /home/${{ env.SERVER_USER }}/${{ env.DEPLOY_DIR }} already exists" | |
fi | |
EOF | |
- name: Deploy to Server | |
id: deploy | |
run: | | |
echo "Deploying .next build to PRODUCTION server..." | |
sshpass -p "${{ env.SERVER_PASSWORD }}" rsync -az --delete --exclude='.next/cache' \ | |
.next public package.json pnpm-lock.yaml .env \ | |
"${{ env.SERVER_USER }}@${{ env.SERVER_IP }}:/home/${{ env.SERVER_USER }}/${{ env.DEPLOY_DIR }}" | |
- name: Start Application on Server | |
id: start | |
run: | | |
sshpass -p "${{ env.SERVER_PASSWORD }}" ssh -o StrictHostKeyChecking=no \ | |
"${{ env.SERVER_USER }}@${{ env.SERVER_IP }}" << 'EOF' | |
set -e # Stop script on error | |
export DEPLOY_NAME="${{ env.DEPLOY_NAME }}" | |
export DEPLOY_PORT="${{ env.DEPLOY_PORT }}" | |
export DEPLOY_DIR="${{ env.DEPLOY_DIR }}" | |
cd /home/${{ env.SERVER_USER }}/$DEPLOY_DIR | |
pnpm install --frozen-lockfile | |
pm2 describe $DEPLOY_NAME > /dev/null 2>&1 && pm2 delete $DEPLOY_NAME || echo "No existing process to delete" | |
PORT=$DEPLOY_PORT pm2 start pnpm --name $DEPLOY_NAME -- start | |
EOF | |
- name: Prepare Status Details | |
id: status | |
if: always() | |
run: | | |
if [ "${{ job.status }}" == "success" ]; then | |
echo "STATUS=success" >> $GITHUB_OUTPUT | |
echo "INCLUDE_DETAILS=true" >> $GITHUB_OUTPUT | |
else | |
echo "STATUS=failed" >> $GITHUB_OUTPUT | |
echo "INCLUDE_DETAILS=false" >> $GITHUB_OUTPUT | |
fi | |
- name: Send Deployment Status Notification | |
if: always() | |
uses: ./.github/actions/slack-notification | |
with: | |
status: ${{ steps.status.outputs.STATUS }} | |
environment: 'PRODUCTION' | |
branch_name: 'main' | |
include_details: ${{ steps.status.outputs.INCLUDE_DETAILS }} | |
webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }} | |
deploy-staging: | |
runs-on: aiadgen | |
needs: [test-and-build] | |
if: github.ref == 'refs/heads/dev' | |
steps: | |
- name: Checkout Repository | |
uses: actions/checkout@v4 | |
- name: Download Build Artifacts | |
uses: actions/download-artifact@v4 | |
with: | |
name: next-build-dev | |
path: . | |
- name: Set Environment Variables | |
run: | | |
echo "ENV_NAME=staging" >> $GITHUB_ENV | |
echo "DEPLOY_PORT=${{ vars.STAGING_DEPLOY_PORT }}" >> $GITHUB_ENV | |
echo "DEPLOY_NAME=${{ vars.STAGING_DEPLOY_NAME }}" >> $GITHUB_ENV | |
echo "DEPLOY_DIR=${{ vars.STAGING_DEPLOY_DIR }}" >> $GITHUB_ENV | |
echo "SERVER_USER=${{ secrets.STAGING_SERVER_USER }}" >> $GITHUB_ENV | |
echo "SERVER_IP=${{ secrets.STAGING_SERVER_IP }}" >> $GITHUB_ENV | |
echo "SERVER_PASSWORD=${{ secrets.STAGING_SERVER_PASSWORD }}" >> $GITHUB_ENV | |
echo "BRANCH_NAME=dev" >> $GITHUB_ENV | |
- name: Send Deployment Start Notification | |
uses: ./.github/actions/slack-notification | |
with: | |
status: 'started' | |
environment: 'STAGING' | |
branch_name: 'dev' | |
webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }} | |
- name: Create Deployment Directory if Not Exists | |
run: | | |
sshpass -p "${{ env.SERVER_PASSWORD }}" ssh -o StrictHostKeyChecking=no \ | |
"${{ env.SERVER_USER }}@${{ env.SERVER_IP }}" << EOF | |
if [ ! -d "/home/${{ env.SERVER_USER }}/${{ env.DEPLOY_DIR }}" ]; then | |
echo "Creating directory /home/${{ env.SERVER_USER }}/${{ env.DEPLOY_DIR }}" | |
mkdir -p /home/${{ env.SERVER_USER }}/${{ env.DEPLOY_DIR }} | |
else | |
echo "Directory /home/${{ env.SERVER_USER }}/${{ env.DEPLOY_DIR }} already exists" | |
fi | |
EOF | |
- name: Deploy to Server | |
id: deploy | |
run: | | |
echo "Deploying .next build to SATAGING server..." | |
sshpass -p "${{ env.SERVER_PASSWORD }}" rsync -az --delete --exclude='.next/cache' \ | |
.next public package.json pnpm-lock.yaml .env \ | |
"${{ env.SERVER_USER }}@${{ env.SERVER_IP }}:/home/${{ env.SERVER_USER }}/${{ env.DEPLOY_DIR }}" | |
- name: Start Application on Server | |
id: start | |
run: | | |
sshpass -p "${{ env.SERVER_PASSWORD }}" ssh -o StrictHostKeyChecking=no \ | |
"${{ env.SERVER_USER }}@${{ env.SERVER_IP }}" << 'EOF' | |
set -e # Stop script on error | |
export DEPLOY_NAME="${{ env.DEPLOY_NAME }}" | |
export DEPLOY_PORT="${{ env.DEPLOY_PORT }}" | |
export DEPLOY_DIR="${{ env.DEPLOY_DIR }}" | |
cd /home/${{ env.SERVER_USER }}/$DEPLOY_DIR | |
pnpm install --frozen-lockfile | |
pm2 describe $DEPLOY_NAME > /dev/null 2>&1 && pm2 delete $DEPLOY_NAME || echo "No existing process to delete" | |
PORT=$DEPLOY_PORT pm2 start pnpm --name $DEPLOY_NAME -- start | |
EOF | |
- name: Prepare Status Details | |
id: status | |
if: always() | |
run: | | |
if [ "${{ job.status }}" == "success" ]; then | |
echo "STATUS=success" >> $GITHUB_OUTPUT | |
echo "INCLUDE_DETAILS=true" >> $GITHUB_OUTPUT | |
else | |
echo "STATUS=failed" >> $GITHUB_OUTPUT | |
echo "INCLUDE_DETAILS=false" >> $GITHUB_OUTPUT | |
fi | |
- name: Send Deployment Status Notification | |
if: always() | |
uses: ./.github/actions/slack-notification | |
with: | |
status: ${{ steps.status.outputs.STATUS }} | |
environment: 'STAGING' | |
branch_name: 'dev' | |
include_details: ${{ steps.status.outputs.INCLUDE_DETAILS }} | |
webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }} |