|
|
|
|
@@ -118,151 +118,7 @@ jobs:
|
|
|
|
|
echo "target=skip" >> "$GITHUB_OUTPUT"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# JOB 2: QA (Lint, Typecheck, Test)
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
qa:
|
|
|
|
|
name: 🧪 QA
|
|
|
|
|
needs: [prepare, deploy]
|
|
|
|
|
if: needs.prepare.outputs.target != 'skip'
|
|
|
|
|
runs-on: docker
|
|
|
|
|
container:
|
|
|
|
|
image: catthehacker/ubuntu:act-latest
|
|
|
|
|
steps:
|
|
|
|
|
- name: Checkout repository
|
|
|
|
|
uses: actions/checkout@v4
|
|
|
|
|
- name: Setup Node.js
|
|
|
|
|
uses: actions/setup-node@v4
|
|
|
|
|
with:
|
|
|
|
|
node-version: 20
|
|
|
|
|
- name: Setup pnpm
|
|
|
|
|
uses: pnpm/action-setup@v3
|
|
|
|
|
with:
|
|
|
|
|
version: 10
|
|
|
|
|
- name: Provide sibling monorepo
|
|
|
|
|
run: |
|
|
|
|
|
git clone https://git.infra.mintel.me/mmintel/at-mintel.git _at-mintel
|
|
|
|
|
|
|
|
|
|
# Force ALL @mintel packages to use the local clone instead of the registry
|
|
|
|
|
# This handles root package.json
|
|
|
|
|
perl -pi -e 's/"\@mintel\/([^"]+)"\s*:\s*"[^"]+"/"\@mintel\/$1": "link:.\/_at-mintel\/packages\/$1"/g' package.json
|
|
|
|
|
# Special case for pdf -> pdf-library
|
|
|
|
|
perl -pi -e 's/link:\.\/_at-mintel\/packages\/pdf"/link:.\/_at-mintel\/packages\/pdf-library"/g' package.json
|
|
|
|
|
|
|
|
|
|
# Handle apps/web/package.json
|
|
|
|
|
perl -pi -e 's/"\@mintel\/([^"]+)"\s*:\s*"[^"]+"/"\@mintel\/$1": "link:..\/\.\.\/_at-mintel\/packages\/$1"/g' apps/web/package.json
|
|
|
|
|
# Special case for pdf -> pdf-library
|
|
|
|
|
perl -pi -e 's/link:\.\.\/\.\.\/_at-mintel\/packages\/pdf"/link:..\/\.\.\/_at-mintel\/packages\/pdf-library"/g' apps/web/package.json
|
|
|
|
|
|
|
|
|
|
# Fix tsconfig paths if they exist
|
|
|
|
|
sed -i 's|../../../at-mintel|../../_at-mintel|g' apps/web/tsconfig.json || true
|
|
|
|
|
|
|
|
|
|
# Fix tsconfig paths if they exist
|
|
|
|
|
sed -i 's|../../../at-mintel|../../_at-mintel|g' apps/web/tsconfig.json || true
|
|
|
|
|
- name: 🔐 Registry Auth
|
|
|
|
|
run: |
|
|
|
|
|
echo "Testing available secrets against git.infra.mintel.me Docker registry..."
|
|
|
|
|
TOKENS="${{ secrets.GITHUB_TOKEN }} ${{ secrets.GITEA_PAT }} ${{ secrets.MINTEL_PRIVATE_TOKEN }} ${{ secrets.NPM_TOKEN }}"
|
|
|
|
|
USERS="${{ github.repository_owner }} ${{ github.actor }} marcmintel mintel mmintel"
|
|
|
|
|
|
|
|
|
|
VALID_TOKEN=""
|
|
|
|
|
VALID_USER=""
|
|
|
|
|
|
|
|
|
|
for T_RAW in $TOKENS; do
|
|
|
|
|
if [ -n "$T_RAW" ]; then
|
|
|
|
|
T=$(echo "$T_RAW" | tr -d ' ' | tr -d '\n' | tr -d '\r')
|
|
|
|
|
|
|
|
|
|
echo "Testing API with token..."
|
|
|
|
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token $T" https://git.infra.mintel.me/api/v1/user || echo "failed")
|
|
|
|
|
echo "API returned: $HTTP_CODE"
|
|
|
|
|
|
|
|
|
|
for U in $USERS; do
|
|
|
|
|
if [ -n "$U" ]; then
|
|
|
|
|
echo "Attempting docker login for a token with user $U..."
|
|
|
|
|
if echo "$T" | docker login git.infra.mintel.me -u "$U" --password-stdin > /dev/null 2>&1; then
|
|
|
|
|
echo "✅ Successfully authenticated with a token."
|
|
|
|
|
VALID_TOKEN="$T"
|
|
|
|
|
VALID_USER="$U"
|
|
|
|
|
break 2
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
if [ -z "$VALID_TOKEN" ]; then
|
|
|
|
|
echo "❌ All token/user combinations failed to authenticate!"
|
|
|
|
|
T=$(echo "$TOKENS" | awk '{print $1}')
|
|
|
|
|
echo "Attempting open diagnostic login with first token and user mmintel..."
|
|
|
|
|
echo "$T" | docker login git.infra.mintel.me -u "mmintel" --password-stdin || true
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
TOKEN="$VALID_TOKEN"
|
|
|
|
|
echo "::add-mask::$TOKEN"
|
|
|
|
|
echo "token=$TOKEN" >> $GITHUB_OUTPUT
|
|
|
|
|
echo "user=$VALID_USER" >> $GITHUB_OUTPUT
|
|
|
|
|
|
|
|
|
|
echo "Configuring .npmrc for git.infra.mintel.me..."
|
|
|
|
|
echo "@mintel:registry=https://git.infra.mintel.me/api/packages/mmintel/npm/" > .npmrc
|
|
|
|
|
echo "//git.infra.mintel.me/api/packages/mmintel/npm/:_authToken=${TOKEN}" >> .npmrc
|
|
|
|
|
echo "always-auth=true" >> .npmrc
|
|
|
|
|
|
|
|
|
|
# Also export for pnpm to pick it up from env if needed
|
|
|
|
|
echo "NPM_TOKEN=${TOKEN}" >> $GITHUB_ENV
|
|
|
|
|
- name: 🏗️ Compile Sibling Monorepo
|
|
|
|
|
timeout-minutes: 15
|
|
|
|
|
run: |
|
|
|
|
|
mkdir -p ci-logs
|
|
|
|
|
echo "=== Compile Sibling Monorepo ===" >> ci-logs/summary.txt
|
|
|
|
|
cp .npmrc _at-mintel/
|
|
|
|
|
cd _at-mintel
|
|
|
|
|
pnpm install --no-frozen-lockfile --loglevel info 2>&1 | tee -a ../ci-logs/summary.txt
|
|
|
|
|
pnpm --filter "...@mintel/payload-ai" \
|
|
|
|
|
--filter @mintel/pdf... \
|
|
|
|
|
--filter @mintel/concept-engine... \
|
|
|
|
|
--filter @mintel/estimation-engine... \
|
|
|
|
|
--filter @mintel/meme-generator... \
|
|
|
|
|
build --loglevel info 2>&1 | tee -a ../ci-logs/summary.txt
|
|
|
|
|
- name: Install dependencies
|
|
|
|
|
timeout-minutes: 10
|
|
|
|
|
run: |
|
|
|
|
|
echo "=== Install dependencies (Root) ===" >> ci-logs/summary.txt
|
|
|
|
|
pnpm install --no-frozen-lockfile --loglevel info 2>&1 | tee -a ci-logs/summary.txt
|
|
|
|
|
- name: 🧪 Test
|
|
|
|
|
if: github.event.inputs.skip_checks != 'true'
|
|
|
|
|
timeout-minutes: 10
|
|
|
|
|
run: |
|
|
|
|
|
echo "=== Test (@mintel/web) ===" >> ci-logs/summary.txt
|
|
|
|
|
pnpm --filter @mintel/web test --loglevel info 2>&1 | tee -a ci-logs/summary.txt
|
|
|
|
|
- name: Inspect on Failure
|
|
|
|
|
if: failure()
|
|
|
|
|
run: |
|
|
|
|
|
echo "==== runner state ===="
|
|
|
|
|
ls -la
|
|
|
|
|
echo "==== _at-mintel state ===="
|
|
|
|
|
ls -la _at-mintel || true
|
|
|
|
|
echo "==== .npmrc check ===="
|
|
|
|
|
cat .npmrc | sed -E 's/authToken=[a-f0-9]{5}.*/authToken=REDACTED/'
|
|
|
|
|
echo "==== pnpm debug logs ===="
|
|
|
|
|
[ -f pnpm-debug.log ] && tail -n 100 pnpm-debug.log || echo "No root pnpm-debug.log"
|
|
|
|
|
[ -f _at-mintel/pnpm-debug.log ] && tail -n 100 _at-mintel/pnpm-debug.log || echo "No sibling pnpm-debug.log"
|
|
|
|
|
- name: Extract QA Error Logs
|
|
|
|
|
if: failure()
|
|
|
|
|
run: |
|
|
|
|
|
mkdir -p ci-logs
|
|
|
|
|
echo "QA Failure Report" > ci-logs/summary.txt
|
|
|
|
|
ls -R >> ci-logs/summary.txt
|
|
|
|
|
[ -f pnpm-debug.log ] && cp pnpm-debug.log ci-logs/ || true
|
|
|
|
|
[ -f _at-mintel/pnpm-debug.log ] && cp _at-mintel/pnpm-debug.log ci-logs/at-mintel-pnpm-debug.log || true
|
|
|
|
|
|
|
|
|
|
SSH_KEY_FILE=$(mktemp)
|
|
|
|
|
echo "${{ secrets.ALPHA_SSH_KEY }}" > "$SSH_KEY_FILE"
|
|
|
|
|
chmod 600 "$SSH_KEY_FILE"
|
|
|
|
|
|
|
|
|
|
ssh -o StrictHostKeyChecking=no -i "$SSH_KEY_FILE" root@alpha.mintel.me "mkdir -p ~/logs"
|
|
|
|
|
scp -r -o StrictHostKeyChecking=no -i "$SSH_KEY_FILE" ci-logs/* root@alpha.mintel.me:~/logs/ || true
|
|
|
|
|
rm "$SSH_KEY_FILE"
|
|
|
|
|
# (JOB 2: QA was removed to reduce pipeline noise)
|
|
|
|
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# JOB 3: Build & Push
|
|
|
|
|
@@ -316,6 +172,8 @@ jobs:
|
|
|
|
|
S3_BUCKET=${{ secrets.S3_BUCKET || vars.S3_BUCKET || 'mintel' }}
|
|
|
|
|
S3_REGION=${{ secrets.S3_REGION || vars.S3_REGION || 'fsn1' }}
|
|
|
|
|
S3_PREFIX=${{ secrets.S3_PREFIX || vars.S3_PREFIX || 'mintel.me' }}
|
|
|
|
|
DATABASE_URI=${{ secrets.DATABASE_URI || (needs.prepare.outputs.target == 'testing' && secrets.TESTING_DIRECTUS_DB_PASSWORD && format('postgres://directus:{0}@postgres-db:5432/directus', secrets.TESTING_DIRECTUS_DB_PASSWORD)) || 'postgres://payload:payload@postgres-db:5432/payload' }}
|
|
|
|
|
PAYLOAD_SECRET=${{ secrets.PAYLOAD_SECRET || 'secret' }}
|
|
|
|
|
BUILD_ID=${{ github.sha }}
|
|
|
|
|
tags: registry.infra.mintel.me/mintel/mintel.me:${{ needs.prepare.outputs.image_tag }}
|
|
|
|
|
cache-from: type=registry,ref=registry.infra.mintel.me/mintel/mintel.me:buildcache-${{ needs.prepare.outputs.target }}
|
|
|
|
|
@@ -560,123 +418,14 @@ jobs:
|
|
|
|
|
if: always()
|
|
|
|
|
run: docker builder prune -f --filter "until=1h"
|
|
|
|
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# JOB 5: Post-Deploy Verification
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
post_deploy_checks:
|
|
|
|
|
name: 🧪 Post-Deploy Verification
|
|
|
|
|
needs: [prepare, deploy, qa]
|
|
|
|
|
if: success() || failure() # Run even if QA fails (due to E2E noise)
|
|
|
|
|
runs-on: docker
|
|
|
|
|
container:
|
|
|
|
|
image: catthehacker/ubuntu:act-latest
|
|
|
|
|
steps:
|
|
|
|
|
- name: Checkout repository
|
|
|
|
|
uses: actions/checkout@v4
|
|
|
|
|
- name: Setup Node.js
|
|
|
|
|
uses: actions/setup-node@v4
|
|
|
|
|
with:
|
|
|
|
|
node-version: 20
|
|
|
|
|
- name: Setup pnpm
|
|
|
|
|
uses: pnpm/action-setup@v3
|
|
|
|
|
with:
|
|
|
|
|
version: 10
|
|
|
|
|
- name: Provide sibling monorepo
|
|
|
|
|
run: |
|
|
|
|
|
git clone https://git.infra.mintel.me/mmintel/at-mintel.git _at-mintel
|
|
|
|
|
|
|
|
|
|
# Force ALL @mintel packages to use the local clone instead of the registry
|
|
|
|
|
perl -pi -e 's/"\@mintel\/([^"]+)"\s*:\s*"[^"]+"/"\@mintel\/$1": "link:.\/_at-mintel\/packages\/$1"/g' package.json
|
|
|
|
|
perl -pi -e 's/link:\.\/_at-mintel\/packages\/pdf"/link:.\/_at-mintel\/packages\/pdf-library"/g' package.json
|
|
|
|
|
perl -pi -e 's/"\@mintel\/([^"]+)"\s*:\s*"[^"]+"/"\@mintel\/$1": "link:..\/\.\.\/_at-mintel\/packages\/$1"/g' apps/web/package.json
|
|
|
|
|
perl -pi -e 's/link:\.\.\/\.\.\/_at-mintel\/packages\/pdf"/link:..\/\.\.\/_at-mintel\/packages\/pdf-library"/g' apps/web/package.json
|
|
|
|
|
|
|
|
|
|
# Fix tsconfig paths if they exist
|
|
|
|
|
sed -i 's|../../../at-mintel|../../_at-mintel|g' apps/web/tsconfig.json || true
|
|
|
|
|
- name: 🔐 Registry Auth
|
|
|
|
|
run: |
|
|
|
|
|
echo "Testing available secrets against git.infra.mintel.me Docker registry..."
|
|
|
|
|
TOKENS="${{ secrets.GITHUB_TOKEN }} ${{ secrets.GITEA_PAT }} ${{ secrets.MINTEL_PRIVATE_TOKEN }} ${{ secrets.NPM_TOKEN }}"
|
|
|
|
|
USERS="${{ github.repository_owner }} ${{ github.actor }} marcmintel mintel mmintel"
|
|
|
|
|
|
|
|
|
|
VALID_TOKEN=""
|
|
|
|
|
for TOKEN_RAW in $TOKENS; do
|
|
|
|
|
if [ -n "$TOKEN_RAW" ]; then
|
|
|
|
|
TOKEN=$(echo "$TOKEN_RAW" | tr -d '[:space:]' | tr -d '\n' | tr -d '\r')
|
|
|
|
|
for U in $USERS; do
|
|
|
|
|
if [ -n "$U" ]; then
|
|
|
|
|
if echo "$TOKEN" | docker login git.infra.mintel.me -u "$U" --password-stdin > /dev/null 2>&1; then
|
|
|
|
|
echo "✅ Successfully authenticated with a token."
|
|
|
|
|
VALID_TOKEN="$TOKEN"
|
|
|
|
|
break 2
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
fi
|
|
|
|
|
done
|
|
|
|
|
if [ -z "$VALID_TOKEN" ]; then echo "❌ All tokens failed to authenticate!"; exit 1; fi
|
|
|
|
|
TOKEN="$VALID_TOKEN"
|
|
|
|
|
echo "Configuring .npmrc for git.infra.mintel.me..."
|
|
|
|
|
echo "@mintel:registry=https://git.infra.mintel.me/api/packages/mmintel/npm/" > .npmrc
|
|
|
|
|
echo "//git.infra.mintel.me/api/packages/mmintel/npm/:_authToken=${TOKEN}" >> .npmrc
|
|
|
|
|
echo "always-auth=true" >> .npmrc
|
|
|
|
|
echo "NPM_TOKEN=${TOKEN}" >> $GITHUB_ENV
|
|
|
|
|
- name: Install dependencies
|
|
|
|
|
run: pnpm install --no-frozen-lockfile
|
|
|
|
|
- name: 🏥 App Health Check
|
|
|
|
|
shell: bash
|
|
|
|
|
env:
|
|
|
|
|
DEPLOY_URL: ${{ needs.prepare.outputs.next_public_url }}
|
|
|
|
|
run: |
|
|
|
|
|
echo "Waiting for app to start at $DEPLOY_URL ..."
|
|
|
|
|
for i in {1..30}; do
|
|
|
|
|
HTTP_CODE=$(curl -sk -o /dev/null -w '%{http_code}' "$DEPLOY_URL" 2>&1) || true
|
|
|
|
|
echo "Attempt $i: HTTP $HTTP_CODE"
|
|
|
|
|
if [[ "$HTTP_CODE" =~ ^2 ]]; then
|
|
|
|
|
echo "✅ App is up (HTTP $HTTP_CODE)"
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
echo "⏳ Waiting... (got $HTTP_CODE)"
|
|
|
|
|
sleep 10
|
|
|
|
|
done
|
|
|
|
|
echo "❌ App health check failed after 30 attempts"
|
|
|
|
|
exit 1
|
|
|
|
|
- name: 🚀 OG Image Check
|
|
|
|
|
continue-on-error: true
|
|
|
|
|
env:
|
|
|
|
|
TEST_URL: ${{ needs.prepare.outputs.next_public_url }}
|
|
|
|
|
run: pnpm --filter @mintel/web check:og
|
|
|
|
|
- name: 📝 E2E Smoke Test
|
|
|
|
|
continue-on-error: true
|
|
|
|
|
env:
|
|
|
|
|
TEST_URL: ${{ needs.prepare.outputs.next_public_url }}
|
|
|
|
|
GATEKEEPER_PASSWORD: ${{ secrets.GATEKEEPER_PASSWORD }}
|
|
|
|
|
PUPPETEER_SKIP_DOWNLOAD: "true"
|
|
|
|
|
PUPPETEER_EXECUTABLE_PATH: /usr/bin/chromium
|
|
|
|
|
run: |
|
|
|
|
|
# Install system Chromium + dependencies (KLZ pattern)
|
|
|
|
|
# Ubuntu's default 'chromium' is a snap wrapper, so we use xtradeb PPA for native binary
|
|
|
|
|
sudo apt-get update && sudo apt-get install -y gnupg wget ca-certificates
|
|
|
|
|
|
|
|
|
|
# Setup xtradeb PPA for native chromium
|
|
|
|
|
CODENAME=$(. /etc/os-release && echo $VERSION_CODENAME)
|
|
|
|
|
sudo mkdir -p /etc/apt/keyrings
|
|
|
|
|
wget -qO- "https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x82BB6851C64F6880" | sudo gpg --dearmor -o /etc/apt/keyrings/xtradeb.gpg || true
|
|
|
|
|
echo "deb [signed-by=/etc/apt/keyrings/xtradeb.gpg] http://ppa.launchpad.net/xtradeb/apps/ubuntu $CODENAME main" | sudo tee /etc/apt/sources.list.d/xtradeb-ppa.list
|
|
|
|
|
printf "Package: *\nPin: release o=LP-PPA-xtradeb-apps\nPin-Priority: 1001\n" | sudo tee /etc/apt/preferences.d/xtradeb
|
|
|
|
|
|
|
|
|
|
sudo apt-get update
|
|
|
|
|
sudo apt-get install -y --allow-downgrades chromium libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxrandr2 libgbm1 libasound2t64 || sudo apt-get install -y --allow-downgrades chromium libnss3 libatk1.0-0 libatk-bridge2.0-0 libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 libxrandr2 libgbm1 libasound2
|
|
|
|
|
|
|
|
|
|
[ -f /usr/bin/chromium ] && sudo ln -sf /usr/bin/chromium /usr/bin/google-chrome
|
|
|
|
|
pnpm --filter @mintel/web check:forms
|
|
|
|
|
# (JOB 5: Post-Deploy Verification was removed to reduce pipeline noise)
|
|
|
|
|
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
# JOB 6: Notifications
|
|
|
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
|
notifications:
|
|
|
|
|
name: 🔔 Notify
|
|
|
|
|
needs: [prepare, deploy, post_deploy_checks]
|
|
|
|
|
needs: [prepare, deploy]
|
|
|
|
|
if: always()
|
|
|
|
|
runs-on: docker
|
|
|
|
|
container:
|
|
|
|
|
@@ -689,7 +438,7 @@ jobs:
|
|
|
|
|
TARGET="${{ needs.prepare.outputs.target }}"
|
|
|
|
|
VERSION="${{ needs.prepare.outputs.image_tag }}"
|
|
|
|
|
|
|
|
|
|
if [[ "$DEPLOY" == "success" ]] && [[ "$SMOKE" == "success" || "$SMOKE" == "skipped" ]]; then
|
|
|
|
|
if [[ "$DEPLOY" == "success" ]]; then
|
|
|
|
|
PRIORITY=5
|
|
|
|
|
EMOJI="✅"
|
|
|
|
|
else
|
|
|
|
|
@@ -699,5 +448,5 @@ jobs:
|
|
|
|
|
|
|
|
|
|
curl -s -k -X POST "${{ secrets.GOTIFY_URL }}/message?token=${{ secrets.GOTIFY_TOKEN }}" \
|
|
|
|
|
-F "title=$EMOJI mintel.me $VERSION -> $TARGET" \
|
|
|
|
|
-F "message=Deploy: $DEPLOY | Smoke: $SMOKE" \
|
|
|
|
|
-F "message=Deploy: $DEPLOY" \
|
|
|
|
|
-F "priority=$PRIORITY" || true
|
|
|
|
|
|