diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..26b7f66 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,60 @@ +# Build outputs and caches +.next/ +coverage/ +dist/ +build/ + +# Dependencies +node_modules/ + +# Environment and config +.env* +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Development and testing +.eslintcache +.nyc_output +playwright-report/ +test-results/ + +# Version control and CI +.git/ +.github/ +.gitignore + +# IDE and editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Documentation +README.md +CHANGELOG.md +docs/ + +# Temporary files +*.tmp +*.temp +tmp/ +temp/ \ No newline at end of file diff --git a/.github/workflows/build_and_push_nonproduction_images.yml b/.github/workflows/build_and_push_nonproduction_images.yml index b915f2b..971071b 100644 --- a/.github/workflows/build_and_push_nonproduction_images.yml +++ b/.github/workflows/build_and_push_nonproduction_images.yml @@ -1,4 +1,4 @@ -name: Build and Push Non-Production Images +name: Build, Test & Push to GHCR on: push: @@ -8,102 +8,134 @@ on: types: [opened, synchronize, reopened] workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + env: REGISTRY: ghcr.io ORG_NAME: refactor-group REPO_NAME: refactor-platform-fe + NODE_ENV: test jobs: - build_and_push_amd64: + # === LINT JOB === + lint: + name: Lint & Format Check runs-on: ubuntu-24.04 - permissions: - contents: read - packages: write - id-token: write + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v5 + with: + node-version: 24.x + cache: 'npm' + cache-dependency-path: package-lock.json + + - name: Install dependencies + run: npm ci --prefer-offline + + - name: Run ESLint + run: npm run lint + + # === TEST JOB === + test: + name: Build & Test + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 - - uses: docker/setup-buildx-action@v3 + - name: Setup Node.js + uses: actions/setup-node@v5 with: - install: true + node-version: 24.x + cache: 'npm' + cache-dependency-path: package-lock.json - - name: Docker login - uses: docker/login-action@v2 + # 🎯 Next.js build cache (job-specific to prevent race conditions) + - name: Cache Next.js build + uses: actions/cache@v4 with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + path: .next/cache + key: ${{ runner.os }}-nextjs-test-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }} + restore-keys: | + ${{ runner.os }}-nextjs-test-${{ hashFiles('**/package-lock.json') }}- + ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}- + + # 🎯 Playwright browser cache + - name: Cache Playwright browsers + uses: actions/cache@v4 + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-playwright- - - name: Set Image Tag - id: vars - run: | - BRANCH_NAME=${GITHUB_HEAD_REF:-${GITHUB_REF##*/}} - IMAGE_BASE="${{ env.REGISTRY }}/${{ env.ORG_NAME }}/${{ env.REPO_NAME }}/${BRANCH_NAME}" - echo "tag=${IMAGE_BASE}:amd64" >> $GITHUB_OUTPUT + - name: Install dependencies + run: npm ci --prefer-offline - - name: Build + Push AMD64 - id: build - uses: docker/build-push-action@v5 - with: - context: . - file: ./Dockerfile - target: runner - platforms: linux/amd64 - push: true - provenance: true - sbom: true - build-args: | - NEXT_PUBLIC_BACKEND_SERVICE_PROTOCOL=${{ vars.BACKEND_SERVICE_PROTOCOL }} - NEXT_PUBLIC_BACKEND_SERVICE_HOST=${{ vars.BACKEND_SERVICE_HOST }} - NEXT_PUBLIC_BACKEND_SERVICE_PORT=${{ vars.BACKEND_SERVICE_PORT }} - NEXT_PUBLIC_BACKEND_SERVICE_API_PATH=${{ vars.BACKEND_SERVICE_API_PATH }} - NEXT_PUBLIC_BACKEND_API_VERSION=${{ vars.BACKEND_API_VERSION }} - NEXT_PUBLIC_TIPTAP_APP_ID=${{ vars.TIPTAP_APP_ID }} - FRONTEND_SERVICE_PORT=${{ vars.FRONTEND_SERVICE_PORT }} - FRONTEND_SERVICE_INTERFACE=${{ vars.FRONTEND_SERVICE_INTERFACE }} - tags: ${{ steps.vars.outputs.tag }} - cache-from: type=gha,scope=amd64 - cache-to: type=gha,mode=max,scope=amd64 + - name: Build application + run: npm run build - build_and_push_arm64: - runs-on: ubuntu-24.04 + - name: Install Playwright browsers + run: npx playwright install --with-deps + + - name: Run unit tests + run: npm run test:run + + - name: Run E2E tests + run: npm run test:e2e + # === DOCKER BUILD & PUSH JOB === + docker: + name: Build & Push Docker Image + runs-on: ubuntu-24.04 + needs: [lint, test] # Only run if lint and test pass + if: github.event_name == 'push' || github.event_name == 'pull_request' permissions: contents: read packages: write + attestations: write id-token: write steps: - - uses: actions/checkout@v4 + - name: Checkout + uses: actions/checkout@v4 - - uses: docker/setup-buildx-action@v3 - with: - install: true + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 - name: Docker login - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Set Image Tag - id: vars + - name: Determine Image Tags + id: tags run: | BRANCH_NAME=${GITHUB_HEAD_REF:-${GITHUB_REF##*/}} IMAGE_BASE="${{ env.REGISTRY }}/${{ env.ORG_NAME }}/${{ env.REPO_NAME }}/${BRANCH_NAME}" - echo "tag=${IMAGE_BASE}:arm64" >> $GITHUB_OUTPUT + echo "frontend_tags=$IMAGE_BASE:latest,$IMAGE_BASE:${{ github.sha }}" >> $GITHUB_OUTPUT + echo "frontend_image_name=$IMAGE_BASE" >> $GITHUB_OUTPUT - - name: Build + Push ARM64 - id: build + - name: Build and Push Frontend Image + id: push_frontend uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile target: runner - platforms: linux/arm64 + platforms: linux/amd64 push: true provenance: true sbom: true @@ -116,6 +148,30 @@ jobs: NEXT_PUBLIC_TIPTAP_APP_ID=${{ vars.TIPTAP_APP_ID }} FRONTEND_SERVICE_PORT=${{ vars.FRONTEND_SERVICE_PORT }} FRONTEND_SERVICE_INTERFACE=${{ vars.FRONTEND_SERVICE_INTERFACE }} - tags: ${{ steps.vars.outputs.tag }} - cache-from: type=gha,scope=arm64 - cache-to: type=gha,mode=max,scope=arm64 + tags: ${{ steps.tags.outputs.frontend_tags }} + cache-from: type=gha,scope=frontend-nonprod + cache-to: type=gha,mode=max,scope=frontend-nonprod + labels: | + org.opencontainers.image.title=Refactor Platform Frontend + org.opencontainers.image.description=Next.js frontend for refactor coaching platform + org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} + org.opencontainers.image.revision=${{ github.sha }} + + - name: Attest Frontend Build + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + uses: actions/attest-build-provenance@v2 + with: + subject-name: ${{ steps.tags.outputs.frontend_image_name }} + subject-digest: ${{ steps.push_frontend.outputs.digest }} + push-to-registry: true + + - name: Print Usage Instructions + run: | + echo "🎉 Build, Test & Push completed successfully!" + echo "" + echo "📦 Frontend Image Pushed:" + echo " docker pull ${{ steps.tags.outputs.frontend_image_name }}:latest" + echo " docker pull ${{ steps.tags.outputs.frontend_image_name }}:${{ github.sha }}" + echo "" + echo "🚀 Run Frontend:" + echo " docker run --rm --env-file .env -p 3000:3000 ${{ steps.tags.outputs.frontend_image_name }}:latest" \ No newline at end of file diff --git a/.github/workflows/build_and_push_production_images.yml b/.github/workflows/build_and_push_production_images.yml index 3acedfd..ebb687e 100644 --- a/.github/workflows/build_and_push_production_images.yml +++ b/.github/workflows/build_and_push_production_images.yml @@ -8,33 +8,63 @@ on: types: [released] # This workflow only runs when a new GitHub release is *actually* released publicly workflow_dispatch: # Also allows manual triggering from GitHub UI +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} # Repository path for the image jobs: build_test_run: + name: Build & Test Frontend runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v4 # Checkout the repository code + - name: Set up Node.js - uses: actions/setup-node@v3 # Set up Node.js environment + uses: actions/setup-node@v5 # Set up Node.js environment with: - node-version: 21.x # Using Node.js 21.x as in the regular CI workflow + node-version: 24.x # Using Node.js 24.x LTS cache: 'npm' # Enable npm caching for faster builds + cache-dependency-path: package-lock.json # Explicit cache invalidation path + + # 🎯 Next.js build cache (production scope to prevent conflicts) + - name: Cache Next.js build + uses: actions/cache@v4 + with: + path: .next/cache + key: ${{ runner.os }}-nextjs-prod-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }} + restore-keys: | + ${{ runner.os }}-nextjs-prod-${{ hashFiles('**/package-lock.json') }}- + ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}- + + # 🎯 Playwright browser cache + - name: Cache Playwright browsers + uses: actions/cache@v4 + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-prod-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-playwright-prod- + ${{ runner.os }}-playwright- # Fallback to any playwright cache - name: Install dependencies - run: npm run ci # Clean Install of dependencies + run: npm ci # Clean Install of dependencies - name: Run build run: npm run build --if-present # Build the Next.js application - - # TODO Uncomment when tests are implemented - # - name: Run tests - # run: npm test # Run tests when they are implemented + - name: Install Playwright browsers + run: npx playwright install --with-deps + - name: Run unit tests + run: npm run test:run + - name: Run E2E tests + run: npm run test:e2e build_and_push_docker: + name: Build & Push Frontend Image runs-on: ubuntu-24.04 - needs: build_test_run + needs: build_test_run # 🎯 Quality gate - only run after tests pass permissions: contents: read # Read repository contents packages: write # Write to GitHub Packages @@ -44,13 +74,8 @@ jobs: steps: - uses: actions/checkout@v4 # Checkout code for the Docker build - - name: Setup QEMU - uses: docker/setup-qemu-action@v2 # Set up QEMU for multi-architecture builds - with: - platforms: linux/amd64,linux/arm64 # Build for both Intel/AMD and ARM architectures - - name: Docker login - uses: docker/login-action@v2 # Log in to GitHub Container Registry + uses: docker/login-action@v3 # Log in to GitHub Container Registry with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -80,7 +105,7 @@ jobs: context: . # Build context is repository root file: ./Dockerfile # Use the Dockerfile at repository root target: runner # Use the runner stage from the Dockerfile - platforms: linux/amd64,linux/arm64 # Multi-architecture build + platforms: linux/amd64 # AMD64 architecture build push: true # Push image to registry provenance: true # Enable provenance metadata sbom: true # Generate Software Bill of Materials @@ -90,11 +115,18 @@ jobs: NEXT_PUBLIC_BACKEND_SERVICE_PORT=${{ vars.BACKEND_SERVICE_PORT }} NEXT_PUBLIC_BACKEND_SERVICE_API_PATH=${{ vars.BACKEND_SERVICE_API_PATH }} NEXT_PUBLIC_BACKEND_API_VERSION=${{ vars.BACKEND_API_VERSION }} + NEXT_PUBLIC_TIPTAP_APP_ID=${{ vars.TIPTAP_APP_ID }} FRONTEND_SERVICE_PORT=${{ vars.FRONTEND_SERVICE_PORT }} FRONTEND_SERVICE_INTERFACE=${{ vars.FRONTEND_SERVICE_INTERFACE }} tags: ${{ steps.tags.outputs.frontend_tags }} # Use "stable" tag from previous step - cache-from: type=gha # Use GitHub Actions cache - cache-to: type=gha,mode=max # Cache for future builds + cache-from: type=gha,scope=frontend-prod # Use GitHub Actions cache with production scope + cache-to: type=gha,mode=max,scope=frontend-prod # Cache for future builds with production scope + labels: | # Enhanced OCI labels + org.opencontainers.image.title=Refactor Platform Frontend + org.opencontainers.image.description=Next.js frontend for refactor coaching platform + org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} + org.opencontainers.image.revision=${{ github.sha }} + org.opencontainers.image.version=stable - name: Show Docker Build Cache (After) run: | # Display cache info after build @@ -123,9 +155,13 @@ jobs: - name: Print Usage Instructions run: | # Print usage instructions - echo "Frontend Image Pushed to ghcr.io as STABLE:" + echo "🎉 Build, Test & Push completed successfully!" + echo "" + echo "📦 Frontend Image Pushed to ghcr.io as STABLE:" echo " docker pull ${{ steps.tags.outputs.frontend_image_name }}:stable" - echo "Run it locally:" + echo "" + echo "🚀 Run it locally:" echo " docker run --rm --env-file .env -p 3000:3000 ${{ steps.tags.outputs.frontend_image_name }}:stable" - echo "Verify signature:" + echo "" + echo "🔐 Verify signature:" echo " cosign verify ${{ steps.tags.outputs.frontend_image_name }}:stable" \ No newline at end of file diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml deleted file mode 100644 index 38877f5..0000000 --- a/.github/workflows/node.js.yml +++ /dev/null @@ -1,60 +0,0 @@ -# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs - -name: Build & Run Tests - -permissions: - contents: read - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Use Node.js 21.x - uses: actions/setup-node@v3 - with: - node-version: 21.x - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Build application - run: npm run build --if-present - - test: - runs-on: ubuntu-latest - needs: build - - steps: - - uses: actions/checkout@v4 - - - name: Use Node.js 21.x - uses: actions/setup-node@v3 - with: - node-version: 21.x - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Build application - run: npm run build --if-present - - - name: Install Playwright browsers - run: npx playwright install --with-deps - - - name: Run unit tests - run: npm run test:run - - - name: Run E2E tests - run: npm run test:e2e \ No newline at end of file diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..cabf43b --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +24 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index f8de3c6..fdfc6cc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Stage 0: Base image -FROM node:22-alpine3.19 AS base +FROM node:24-alpine AS base # BuildKit Platform Context (used for metadata, not to alter FROM) ARG BUILDPLATFORM diff --git a/package-lock.json b/package-lock.json index f9c481c..f0cb7fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -75,7 +75,7 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", - "@types/node": "^22.10.2", + "@types/node": "^24.0.0", "@types/react": "19.1.8", "@types/react-dom": "19.1.6", "@vitejs/plugin-react": "^4.6.0", @@ -83,7 +83,7 @@ "@vitest/ui": "^2.1.9", "autoprefixer": "^10.4.20", "eslint": "^8.57.1", - "eslint-config-next": "15.3.5", + "eslint-config-next": "15.4.7", "jsdom": "^26.1.0", "msw": "^2.10.3", "postcss": "^8.4.49", @@ -1885,9 +1885,9 @@ "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "15.3.5", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.3.5.tgz", - "integrity": "sha512-BZwWPGfp9po/rAnJcwUBaM+yT/+yTWIkWdyDwc74G9jcfTrNrmsHe+hXHljV066YNdVs8cxROxX5IgMQGX190w==", + "version": "15.4.7", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.4.7.tgz", + "integrity": "sha512-asj3RRiEruRLVr+k2ZC4hll9/XBzegMpFMr8IIRpNUYypG86m/a76339X2WETl1C53A512w2INOc2KZV769KPA==", "dev": true, "license": "MIT", "dependencies": { @@ -4715,13 +4715,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.17.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.2.tgz", - "integrity": "sha512-gL6z5N9Jm9mhY+U2KXZpteb+09zyffliRkZyZOHODGATyC5B1Jt/7TzuuiLkFsSUMLbS1OLmlj/E+/3KF4Q/4w==", + "version": "24.5.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", + "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.12.0" } }, "node_modules/@types/react": { @@ -7174,13 +7174,13 @@ } }, "node_modules/eslint-config-next": { - "version": "15.3.5", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.3.5.tgz", - "integrity": "sha512-oQdvnIgP68wh2RlR3MdQpvaJ94R6qEFl+lnu8ZKxPj5fsAHrSF/HlAOZcsimLw3DT6bnEQIUdbZC2Ab6sWyptg==", + "version": "15.4.7", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.4.7.tgz", + "integrity": "sha512-tkKKNVJKI4zMIgTpvG2x6mmdhuOdgXUL3AaSPHwxLQkvzi4Yryqvk6B0R5Z4gkpe7FKopz3ZmlpePH3NTHy3gA==", "dev": true, "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "15.3.5", + "@next/eslint-plugin-next": "15.4.7", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", @@ -12570,9 +12570,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", + "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index a6be95b..60d24fe 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", - "@types/node": "^22.10.2", + "@types/node": "^24.0.0", "@types/react": "19.1.8", "@types/react-dom": "19.1.6", "@vitejs/plugin-react": "^4.6.0", @@ -92,7 +92,7 @@ "@vitest/ui": "^2.1.9", "autoprefixer": "^10.4.20", "eslint": "^8.57.1", - "eslint-config-next": "15.3.5", + "eslint-config-next": "15.4.7", "jsdom": "^26.1.0", "msw": "^2.10.3", "postcss": "^8.4.49",