diff --git a/.github/workflows/official-docs-trigger.yml b/.github/workflows/official-docs-trigger.yml index 80b2e06d4..59508e9f2 100644 --- a/.github/workflows/official-docs-trigger.yml +++ b/.github/workflows/official-docs-trigger.yml @@ -1,191 +1,191 @@ -# ============================================================================= -# Documentation Website Build and Deployment Workflow -# ============================================================================= -# This workflow generates and deploys the documentation website -# (docs.arolariu.ro) using Docusaurus 3.10 as the shell, with four -# extractors feeding into it (TypeDoc for the TS reference, -# DefaultDocumentation for .NET internals, pydoc-markdown for the -# Python experimental service, plus a build-time OpenAPI spec copy). -# -# Architecture: -# - docs:assemble: Runs all four extractors + normalizer + landing pages -# - npm run build:docs: Docusaurus build (fires the postbuild hook -# that copies staticwebapp.config.json and emits llms.txt) -# - Azure Static Web Apps: Hosts the generated documentation -# -# Triggers: -# - Automatic: Push to 'main' branch (excluding .github/ changes) -# - Manual: workflow_dispatch for on-demand documentation regeneration -# -# Technology Stack: -# - Docusaurus 3.10: Site shell -# - TypeDoc + DefaultDocumentation + pydoc-markdown: Extractors -# - .NET 10.0: Required for building assemblies whose XML docs feed DefaultDocumentation -# - Python 3.12: Required for pydoc-markdown -# - Azure Static Web Apps: Global CDN hosting -# -# Security: -# - Uses OIDC for Azure authentication -# - Static Web Apps deployment token stored in GitHub Secrets -# -# Prerequisites: -# 1. Docusaurus config in sites/docs.arolariu.ro/docusaurus.config.ts -# 2. .NET solution with XML documentation enabled -# 3. GitHub Secrets configured: -# - AZURE_STATIC_WEB_APPS_DOCS_TOKEN -# -# Output: -# - URL: https://docs.arolariu.ro -# - Content: Unified developer reference across three services -# ============================================================================= - -name: "official-docs-trigger" - -permissions: - id-token: write # Required for OIDC authentication with Azure - contents: read # Required to checkout repository - -on: - # Automatic trigger on main branch changes (except .github/) - push: - branches: ["main"] - paths-ignore: [".github/*"] - - # Manual trigger for on-demand documentation regeneration - workflow_dispatch: - inputs: - environment: - description: "Target environment for deployment" - options: - - "production" - default: "production" - type: choice - -env: - COMMIT_SHA: ${{ github.sha }} - -jobs: - build-and-deploy: - if: github.repository == 'arolariu/arolariu.ro' - name: ๐Ÿš€ Building and deploying the docs platform (docs.arolariu.ro)... - runs-on: ubuntu-latest - environment: - name: "documentation" - url: "https://docs.arolariu.ro" - steps: - - name: ๐Ÿ›ซ Checking out the branch inside the runner environment... - uses: actions/checkout@v6 - - - name: ๐Ÿ“ Display workflow context - run: | - echo "::group::๐Ÿ” Workflow Context" - echo "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" - echo "โ”‚ ๐Ÿ“š DOCUMENTATION WEBSITE PIPELINE โ”‚" - echo "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค" - echo "โ”‚ ๐Ÿ“ฆ Job: Build & Deploy โ”‚" - echo "โ”‚ ๐ŸŒ Framework: Docusaurus 3.10 โ”‚" - echo "โ”‚ ๐ŸŽฏ Target: Azure Static Web Apps โ”‚" - echo "โ”‚ ๐Ÿท๏ธ Commit SHA: ${{ github.sha }}" - echo "โ”‚ ๐Ÿ‘ค Triggered by: ${{ github.actor }}" - echo "โ”‚ ๐Ÿ“… Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" - echo "โ”‚ ๐Ÿ”— Run ID: ${{ github.run_id }}" - echo "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" - echo "::endgroup::" - - # Write initial summary - echo "## ๐Ÿ“š Documentation Website Pipeline" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Parameter | Value |" >> $GITHUB_STEP_SUMMARY - echo "|-----------|-------|" >> $GITHUB_STEP_SUMMARY - echo "| Framework | Docusaurus 3.10 |" >> $GITHUB_STEP_SUMMARY - echo "| Target | Azure Static Web Apps |" >> $GITHUB_STEP_SUMMARY - echo "| URL | https://docs.arolariu.ro |" >> $GITHUB_STEP_SUMMARY - - - name: ๐Ÿ“ฆ Setup workspace - uses: ./.github/actions/setup-workspace - with: - dotnet-version: '10' - install-node-dependencies: 'true' - install-dotnet-dependencies: 'true' - cache-key-prefix: 'docs' - - - name: ๐Ÿ Setting up Python 3.12 for pydoc-markdown... - uses: actions/setup-python@v5 - with: - python-version: '3.12' - cache: 'pip' - - - name: "[DOCS::1/4] ๐Ÿ—๏ธ Installing the .NET dependencies..." - run: | - echo "::group::๐Ÿ“ฆ .NET Dependencies" - echo "Installing .NET workloads, restoring packages, and installing DefaultDocumentation.Console..." - START_TIME=$(date +%s) - dotnet workload restore - dotnet restore ./arolariu.slnx - dotnet tool install --global DefaultDocumentation.Console --version 1.2.4 - echo "${HOME}/.dotnet/tools" >> $GITHUB_PATH - python -m pip install -r sites/exp.arolariu.ro/requirements-dev.txt - END_TIME=$(date +%s) - DURATION=$((END_TIME - START_TIME)) - echo "::notice::โœ… Dependencies installed in ${DURATION}s" - echo "::endgroup::" - - - name: "[DOCS::2/4] ๐Ÿงพ Assembling extractor output (TypeDoc + pydoc-markdown + DefaultDocumentation)..." - run: | - echo "::group::๐Ÿ“ Docs assemble" - echo "Running the four extractors and the frontmatter normalizer..." - START_TIME=$(date +%s) - npm run docs:assemble - END_TIME=$(date +%s) - DURATION=$((END_TIME - START_TIME)) - echo "::notice::โœ… Assemble completed in ${DURATION}s" - echo "::endgroup::" - - echo "" >> $GITHUB_STEP_SUMMARY - echo "### ๐Ÿ“ Assemble Results" >> $GITHUB_STEP_SUMMARY - echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY - echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY - echo "| Duration | ${DURATION}s |" >> $GITHUB_STEP_SUMMARY - - - name: "[DOCS::3/4] โš™๏ธ Building the Docusaurus documentation platform..." - run: | - echo "::group::๐Ÿ“š Docusaurus Build" - echo "Building the Docusaurus static site (postbuild hook copies the SWA config and emits llms.txt)..." - START_TIME=$(date +%s) - npm run build:docs - END_TIME=$(date +%s) - DURATION=$((END_TIME - START_TIME)) - echo "::notice::โœ… Docusaurus build completed in ${DURATION}s" - echo "::endgroup::" - - echo "" >> $GITHUB_STEP_SUMMARY - echo "### ๐Ÿ“š Docusaurus Results" >> $GITHUB_STEP_SUMMARY - echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY - echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY - echo "| Duration | ${DURATION}s |" >> $GITHUB_STEP_SUMMARY - echo "| Output | sites/docs.arolariu.ro/build |" >> $GITHUB_STEP_SUMMARY - - - name: "[DOCS::4/4] ๐Ÿš€ Deploying documentation..." - uses: Azure/static-web-apps-deploy@v1 - with: - azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_DOCS_TOKEN }} - action: "upload" - app_location: "./sites/docs.arolariu.ro/build" # App source code path - output_location: "./sites/docs.arolariu.ro/build" - - - name: ๐Ÿ“Š Deployment summary - run: | - echo "" >> $GITHUB_STEP_SUMMARY - echo "### โœ… Deployment Successful" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "๐Ÿ”— **Live URL**: https://docs.arolariu.ro" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Deployed at: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY - - echo "" - echo "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" - echo "โ”‚ โœ… DOCUMENTATION DEPLOYED SUCCESSFULLY โ”‚" - echo "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค" - echo "โ”‚ ๐Ÿ”— URL: https://docs.arolariu.ro โ”‚" - echo "โ”‚ ๐Ÿ“… Time: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" - echo "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" +# ============================================================================= +# Documentation Website Build and Deployment Workflow +# ============================================================================= +# This workflow generates and deploys the documentation website +# (docs.arolariu.ro) using Docusaurus 3.10 as the shell, with four +# extractors feeding into it (TypeDoc for the TS reference, +# DefaultDocumentation for .NET internals, pydoc-markdown for the +# Python experimental service, plus a build-time OpenAPI spec copy). +# +# Architecture: +# - docs:assemble: Runs all four extractors + normalizer + landing pages +# - npm run build:docs: Docusaurus build (fires the postbuild hook +# that copies staticwebapp.config.json and emits llms.txt) +# - Azure Static Web Apps: Hosts the generated documentation +# +# Triggers: +# - Automatic: Push to 'main' branch (excluding .github/ changes) +# - Manual: workflow_dispatch for on-demand documentation regeneration +# +# Technology Stack: +# - Docusaurus 3.10: Site shell +# - TypeDoc + DefaultDocumentation + pydoc-markdown: Extractors +# - .NET 10.0: Required for building assemblies whose XML docs feed DefaultDocumentation +# - Python 3.12: Required for pydoc-markdown +# - Azure Static Web Apps: Global CDN hosting +# +# Security: +# - Uses OIDC for Azure authentication +# - Static Web Apps deployment token stored in GitHub Secrets +# +# Prerequisites: +# 1. Docusaurus config in sites/docs.arolariu.ro/docusaurus.config.ts +# 2. .NET solution with XML documentation enabled +# 3. GitHub Secrets configured: +# - AZURE_STATIC_WEB_APPS_DOCS_TOKEN +# +# Output: +# - URL: https://docs.arolariu.ro +# - Content: Unified developer reference across three services +# ============================================================================= + +name: "official-docs-trigger" + +permissions: + id-token: write # Required for OIDC authentication with Azure + contents: read # Required to checkout repository + +on: + # Automatic trigger on main branch changes (except .github/) + push: + branches: ["main"] + paths-ignore: [".github/*"] + + # Manual trigger for on-demand documentation regeneration + workflow_dispatch: + inputs: + environment: + description: "Target environment for deployment" + options: + - "production" + default: "production" + type: choice + +env: + COMMIT_SHA: ${{ github.sha }} + +jobs: + build-and-deploy: + if: github.repository == 'arolariu/arolariu.ro' + name: ๐Ÿš€ Building and deploying the docs platform (docs.arolariu.ro)... + runs-on: ubuntu-latest + environment: + name: "documentation" + url: "https://docs.arolariu.ro" + steps: + - name: ๐Ÿ›ซ Checking out the branch inside the runner environment... + uses: actions/checkout@v6 + + - name: ๐Ÿ“ Display workflow context + run: | + echo "::group::๐Ÿ” Workflow Context" + echo "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" + echo "โ”‚ ๐Ÿ“š DOCUMENTATION WEBSITE PIPELINE โ”‚" + echo "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค" + echo "โ”‚ ๐Ÿ“ฆ Job: Build & Deploy โ”‚" + echo "โ”‚ ๐ŸŒ Framework: Docusaurus 3.10 โ”‚" + echo "โ”‚ ๐ŸŽฏ Target: Azure Static Web Apps โ”‚" + echo "โ”‚ ๐Ÿท๏ธ Commit SHA: ${{ github.sha }}" + echo "โ”‚ ๐Ÿ‘ค Triggered by: ${{ github.actor }}" + echo "โ”‚ ๐Ÿ“… Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" + echo "โ”‚ ๐Ÿ”— Run ID: ${{ github.run_id }}" + echo "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" + echo "::endgroup::" + + # Write initial summary + echo "## ๐Ÿ“š Documentation Website Pipeline" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Parameter | Value |" >> $GITHUB_STEP_SUMMARY + echo "|-----------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Framework | Docusaurus 3.10 |" >> $GITHUB_STEP_SUMMARY + echo "| Target | Azure Static Web Apps |" >> $GITHUB_STEP_SUMMARY + echo "| URL | https://docs.arolariu.ro |" >> $GITHUB_STEP_SUMMARY + + - name: ๐Ÿ“ฆ Setup workspace + uses: ./.github/actions/setup-workspace + with: + dotnet-version: '10' + install-node-dependencies: 'true' + install-dotnet-dependencies: 'true' + cache-key-prefix: 'docs' + + - name: ๐Ÿ Setting up Python 3.12 for pydoc-markdown... + uses: actions/setup-python@v6 + with: + python-version: '3.12' + cache: 'pip' + + - name: "[DOCS::1/4] ๐Ÿ—๏ธ Installing the .NET dependencies..." + run: | + echo "::group::๐Ÿ“ฆ .NET Dependencies" + echo "Installing .NET workloads, restoring packages, and installing DefaultDocumentation.Console..." + START_TIME=$(date +%s) + dotnet workload restore + dotnet restore ./arolariu.slnx + dotnet tool install --global DefaultDocumentation.Console --version 1.2.4 + echo "${HOME}/.dotnet/tools" >> $GITHUB_PATH + python -m pip install -r sites/exp.arolariu.ro/requirements-dev.txt + END_TIME=$(date +%s) + DURATION=$((END_TIME - START_TIME)) + echo "::notice::โœ… Dependencies installed in ${DURATION}s" + echo "::endgroup::" + + - name: "[DOCS::2/4] ๐Ÿงพ Assembling extractor output (TypeDoc + pydoc-markdown + DefaultDocumentation)..." + run: | + echo "::group::๐Ÿ“ Docs assemble" + echo "Running the four extractors and the frontmatter normalizer..." + START_TIME=$(date +%s) + npm run docs:assemble + END_TIME=$(date +%s) + DURATION=$((END_TIME - START_TIME)) + echo "::notice::โœ… Assemble completed in ${DURATION}s" + echo "::endgroup::" + + echo "" >> $GITHUB_STEP_SUMMARY + echo "### ๐Ÿ“ Assemble Results" >> $GITHUB_STEP_SUMMARY + echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY + echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Duration | ${DURATION}s |" >> $GITHUB_STEP_SUMMARY + + - name: "[DOCS::3/4] โš™๏ธ Building the Docusaurus documentation platform..." + run: | + echo "::group::๐Ÿ“š Docusaurus Build" + echo "Building the Docusaurus static site (postbuild hook copies the SWA config and emits llms.txt)..." + START_TIME=$(date +%s) + npm run build:docs + END_TIME=$(date +%s) + DURATION=$((END_TIME - START_TIME)) + echo "::notice::โœ… Docusaurus build completed in ${DURATION}s" + echo "::endgroup::" + + echo "" >> $GITHUB_STEP_SUMMARY + echo "### ๐Ÿ“š Docusaurus Results" >> $GITHUB_STEP_SUMMARY + echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY + echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Duration | ${DURATION}s |" >> $GITHUB_STEP_SUMMARY + echo "| Output | sites/docs.arolariu.ro/build |" >> $GITHUB_STEP_SUMMARY + + - name: "[DOCS::4/4] ๐Ÿš€ Deploying documentation..." + uses: Azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_DOCS_TOKEN }} + action: "upload" + app_location: "./sites/docs.arolariu.ro/build" # App source code path + output_location: "./sites/docs.arolariu.ro/build" + + - name: ๐Ÿ“Š Deployment summary + run: | + echo "" >> $GITHUB_STEP_SUMMARY + echo "### โœ… Deployment Successful" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "๐Ÿ”— **Live URL**: https://docs.arolariu.ro" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Deployed at: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY + + echo "" + echo "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" + echo "โ”‚ โœ… DOCUMENTATION DEPLOYED SUCCESSFULLY โ”‚" + echo "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค" + echo "โ”‚ ๐Ÿ”— URL: https://docs.arolariu.ro โ”‚" + echo "โ”‚ ๐Ÿ“… Time: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" + echo "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" diff --git a/.github/workflows/official-e2e-action.yml b/.github/workflows/official-e2e-action.yml index 6b7fd0d30..d5abba59b 100644 --- a/.github/workflows/official-e2e-action.yml +++ b/.github/workflows/official-e2e-action.yml @@ -1,274 +1,274 @@ -# ============================================================================= -# End-to-End (E2E) Live Testing Workflow -# ============================================================================= -# Newman-based live testing for frontend, backend, and CV targets. -# -# Features: -# - Matrix execution for frontend/backend/cv -# - Deterministic artifact naming for reports and logs -# - Concurrency control to avoid overlapping live runs -# - Automatic issue creation with parsed diagnostics on failure -# ============================================================================= - -name: "official-e2e-action" - -permissions: - contents: read - -on: - schedule: - - cron: "17 6 * * *" - - workflow_dispatch: - inputs: - strict_mode: - default: false - description: "Enable Newman strict mode (--bail)." - required: false - type: boolean - targets: - default: "all" - description: "Target selection for manual runs." - options: - - all - - frontend - - backend - - cv - required: false - type: choice - -concurrency: - group: e2e-live-${{ github.ref }} - cancel-in-progress: true - -jobs: - prepare: - if: github.repository == 'arolariu/arolariu.ro' - name: ๐Ÿ“‹ Prepare E2E matrix - outputs: - strict-mode: ${{ steps.resolve.outputs.strict_mode }} - targets: ${{ steps.resolve.outputs.targets }} - runs-on: ubuntu-latest - timeout-minutes: 10 - - steps: - - name: ๐Ÿ“ฅ Checkout repository - uses: actions/checkout@v6 - - - name: ๐Ÿš€ Setup workspace - uses: ./.github/actions/setup-workspace - with: - cache-key-prefix: "e2e" - dotnet-version: "" - node-version: "24" - - - name: ๐Ÿ” Resolve run inputs - id: resolve - shell: bash - run: | - selected_targets="all" - strict_mode="false" - - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - selected_targets="${{ github.event.inputs.targets }}" - if [ "${{ github.event.inputs.strict_mode }}" = "true" ]; then - strict_mode="true" - fi - fi - - case "$selected_targets" in - all) - targets='["frontend","backend","cv"]' - ;; - frontend|backend|cv) - targets="[\"$selected_targets\"]" - ;; - *) - echo "::error::Invalid targets input: $selected_targets" - exit 1 - ;; - esac - - echo "targets=$targets" >> "$GITHUB_OUTPUT" - echo "strict_mode=$strict_mode" >> "$GITHUB_OUTPUT" - - { - echo "## ๐Ÿงช E2E Live Test Configuration" - echo "" - echo "| Parameter | Value |" - echo "|-----------|-------|" - echo "| Trigger | ${{ github.event_name }} |" - echo "| Targets | $targets |" - echo "| Strict Mode | $strict_mode |" - } >> "$GITHUB_STEP_SUMMARY" - - e2e-live: - if: github.repository == 'arolariu/arolariu.ro' - name: ๐Ÿงช ${{ matrix.target }} live tests - needs: prepare - runs-on: ubuntu-latest - timeout-minutes: 45 - - strategy: - fail-fast: false - matrix: - target: ${{ fromJson(needs.prepare.outputs.targets) }} - - steps: - - name: ๐Ÿ“ฅ Checkout repository - uses: actions/checkout@v6 - - - name: ๐Ÿš€ Setup workspace - uses: ./.github/actions/setup-workspace - with: - cache-key-prefix: "e2e" - dotnet-version: "" - node-version: "24" - - - name: ๐ŸŒก๏ธ Backend health check - if: matrix.target == 'backend' - shell: bash - run: | - http_code=$(curl -s -o backend-health.json -w "%{http_code}" https://api.arolariu.ro/health) - if [ "$http_code" != "200" ]; then - echo "::error::Backend health check failed with HTTP $http_code" - cat backend-health.json - exit 1 - fi - - - name: ๐Ÿš€ Warm up frontend runtime - if: matrix.target == 'frontend' - shell: bash - run: | - echo "Priming frontend routes to reduce cold-start latency..." - for endpoint in "/" "/api/user"; do - status_code=$(curl -sS -o /dev/null -w "%{http_code}" "https://arolariu.ro${endpoint}" || echo "000") - echo "Warmup ${endpoint} -> HTTP ${status_code}" - sleep 5 - done - - - name: ๐Ÿงช Run Newman collection - id: run-tests - shell: bash - run: | - mkdir -p e2e-logs - start_time=$(date +%s) - - set +e - npm run test:e2e:${{ matrix.target }} 2>&1 | tee e2e-${{ matrix.target }}.log - exit_code=${PIPESTATUS[0]} - set -e - - end_time=$(date +%s) - duration=$((end_time - start_time)) - status="success" - if [ "$exit_code" -ne 0 ]; then - status="failure" - fi - - echo "duration=${duration}s" >> "$GITHUB_OUTPUT" - echo "status=$status" >> "$GITHUB_OUTPUT" - - { - echo "### ${{ matrix.target }} E2E result" - echo "" - echo "| Metric | Value |" - echo "|--------|-------|" - echo "| Status | $status |" - echo "| Duration | ${duration}s |" - } >> "$GITHUB_STEP_SUMMARY" - - if [ "$exit_code" -ne 0 ]; then - exit "$exit_code" - fi - env: - E2E_TEST_AUTH_TOKEN: ${{ secrets.E2E_TEST_AUTH_TOKEN }} - E2E_TEST_ENVIRONMENT: production - NEWMAN_REPORT_DIR: e2e-logs - NEWMAN_STRICT_MODE: ${{ needs.prepare.outputs['strict-mode'] }} - - - name: ๐Ÿ“Š Summarize Newman metrics - if: always() - shell: bash - run: | - report_path="e2e-logs/newman-${{ matrix.target }}.json" - if [ ! -f "$report_path" ]; then - echo "::warning::Report not found: $report_path" - exit 0 - fi - - node -e "const fs=require('node:fs');const p=process.argv[1];const target=process.argv[2];const r=JSON.parse(fs.readFileSync(p,'utf-8'));const stats=r?.run?.stats??{};const timings=r?.run?.timings??{};const requests=stats.requests??{};const assertions=stats.assertions??{};const totalMs=(timings.completed??0)-(timings.started??0);const summary=['','### '+target+' Newman stats','','| Metric | Value |','|--------|-------|','| Requests | '+(requests.total??0)+' total / '+(requests.failed??0)+' failed |','| Assertions | '+(assertions.total??0)+' total / '+(assertions.failed??0)+' failed |','| Runtime | '+(totalMs>0?((totalMs/1000).toFixed(2)+'s'):'N/A')+' |'];fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY,summary.join('\\n')+'\\n');" "$report_path" "${{ matrix.target }}" - - - name: ๐Ÿงพ Persist target status - if: always() - shell: bash - run: | - cat > "e2e-status-${{ matrix.target }}.json" <> "$GITHUB_OUTPUT" + echo "strict_mode=$strict_mode" >> "$GITHUB_OUTPUT" + + { + echo "## ๐Ÿงช E2E Live Test Configuration" + echo "" + echo "| Parameter | Value |" + echo "|-----------|-------|" + echo "| Trigger | ${{ github.event_name }} |" + echo "| Targets | $targets |" + echo "| Strict Mode | $strict_mode |" + } >> "$GITHUB_STEP_SUMMARY" + + e2e-live: + if: github.repository == 'arolariu/arolariu.ro' + name: ๐Ÿงช ${{ matrix.target }} live tests + needs: prepare + runs-on: ubuntu-latest + timeout-minutes: 45 + + strategy: + fail-fast: false + matrix: + target: ${{ fromJson(needs.prepare.outputs.targets) }} + + steps: + - name: ๐Ÿ“ฅ Checkout repository + uses: actions/checkout@v6 + + - name: ๐Ÿš€ Setup workspace + uses: ./.github/actions/setup-workspace + with: + cache-key-prefix: "e2e" + dotnet-version: "" + node-version: "24" + + - name: ๐ŸŒก๏ธ Backend health check + if: matrix.target == 'backend' + shell: bash + run: | + http_code=$(curl -s -o backend-health.json -w "%{http_code}" https://api.arolariu.ro/health) + if [ "$http_code" != "200" ]; then + echo "::error::Backend health check failed with HTTP $http_code" + cat backend-health.json + exit 1 + fi + + - name: ๐Ÿš€ Warm up frontend runtime + if: matrix.target == 'frontend' + shell: bash + run: | + echo "Priming frontend routes to reduce cold-start latency..." + for endpoint in "/" "/api/user"; do + status_code=$(curl -sS -o /dev/null -w "%{http_code}" "https://arolariu.ro${endpoint}" || echo "000") + echo "Warmup ${endpoint} -> HTTP ${status_code}" + sleep 5 + done + + - name: ๐Ÿงช Run Newman collection + id: run-tests + shell: bash + run: | + mkdir -p e2e-logs + start_time=$(date +%s) + + set +e + npm run test:e2e:${{ matrix.target }} 2>&1 | tee e2e-${{ matrix.target }}.log + exit_code=${PIPESTATUS[0]} + set -e + + end_time=$(date +%s) + duration=$((end_time - start_time)) + status="success" + if [ "$exit_code" -ne 0 ]; then + status="failure" + fi + + echo "duration=${duration}s" >> "$GITHUB_OUTPUT" + echo "status=$status" >> "$GITHUB_OUTPUT" + + { + echo "### ${{ matrix.target }} E2E result" + echo "" + echo "| Metric | Value |" + echo "|--------|-------|" + echo "| Status | $status |" + echo "| Duration | ${duration}s |" + } >> "$GITHUB_STEP_SUMMARY" + + if [ "$exit_code" -ne 0 ]; then + exit "$exit_code" + fi + env: + E2E_TEST_AUTH_TOKEN: ${{ secrets.E2E_TEST_AUTH_TOKEN }} + E2E_TEST_ENVIRONMENT: production + NEWMAN_REPORT_DIR: e2e-logs + NEWMAN_STRICT_MODE: ${{ needs.prepare.outputs['strict-mode'] }} + + - name: ๐Ÿ“Š Summarize Newman metrics + if: always() + shell: bash + run: | + report_path="e2e-logs/newman-${{ matrix.target }}.json" + if [ ! -f "$report_path" ]; then + echo "::warning::Report not found: $report_path" + exit 0 + fi + + node -e "const fs=require('node:fs');const p=process.argv[1];const target=process.argv[2];const r=JSON.parse(fs.readFileSync(p,'utf-8'));const stats=r?.run?.stats??{};const timings=r?.run?.timings??{};const requests=stats.requests??{};const assertions=stats.assertions??{};const totalMs=(timings.completed??0)-(timings.started??0);const summary=['','### '+target+' Newman stats','','| Metric | Value |','|--------|-------|','| Requests | '+(requests.total??0)+' total / '+(requests.failed??0)+' failed |','| Assertions | '+(assertions.total??0)+' total / '+(assertions.failed??0)+' failed |','| Runtime | '+(totalMs>0?((totalMs/1000).toFixed(2)+'s'):'N/A')+' |'];fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY,summary.join('\\n')+'\\n');" "$report_path" "${{ matrix.target }}" + + - name: ๐Ÿงพ Persist target status + if: always() + shell: bash + run: | + cat > "e2e-status-${{ matrix.target }}.json" <> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Parameter | Value |" >> $GITHUB_STEP_SUMMARY - echo "|-----------|-------|" >> $GITHUB_STEP_SUMMARY - echo "| PR | #${{ github.event.pull_request.number || 'N/A' }} |" >> $GITHUB_STEP_SUMMARY - echo "| Author | @${{ github.event.pull_request.user.login || github.actor }} |" >> $GITHUB_STEP_SUMMARY - echo "| Head SHA | \`${{ env.HEAD_REF }}\` |" >> $GITHUB_STEP_SUMMARY - - - name: ๐Ÿ“ฆ Setup workspace - uses: ./.github/actions/setup-workspace - with: - node-version: ${{ env.NODE_VERSION }} - cache-key-prefix: 'hygiene-${{ github.event.pull_request.number || github.run_id }}' - install-node-dependencies: 'true' - - - name: ๐Ÿ”Ž Detect changes - id: detect - uses: actions/github-script@v8 - env: - CHECK_MODE: "detect" - with: - script: | - const { execSync } = require('child_process'); - execSync('cd .github/scripts && npm ci', { stdio: 'inherit' }); - const { default: runHygieneCheck } = await import('${{ github.workspace }}/.github/scripts/src/runHygieneCheckV2.ts'); - await runHygieneCheck(); - - # ============================================================================ - # FORMAT CHECK - # ============================================================================ - format: - name: ๐ŸŽจ Format Check - runs-on: ubuntu-latest - timeout-minutes: 10 - needs: setup - if: needs.setup.outputs.has-changes == 'true' || github.event_name == 'workflow_dispatch' - continue-on-error: true - steps: - - name: ๐Ÿ›ซ Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 1 - ref: ${{ env.HEAD_REF }} - - - name: ๐Ÿ“ Display check context - run: | - echo "::group::๐Ÿ” Format Check Context" - echo "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" - echo "โ”‚ ๐ŸŽจ FORMAT CHECK โ”‚" - echo "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค" - echo "โ”‚ ๐Ÿ”ง Tool: Prettier โ”‚" - echo "โ”‚ ๐Ÿ“ Scope: All supported files โ”‚" - echo "โ”‚ ๐Ÿ”€ Changed files: ${{ needs.setup.outputs.changed-files-count || 'calculating...' }}" - echo "โ”‚ ๐Ÿ“… Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" - echo "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" - echo "::endgroup::" - - - name: ๐Ÿ“ฆ Setup workspace - uses: ./.github/actions/setup-workspace - with: - node-version: ${{ env.NODE_VERSION }} - cache-key-prefix: 'hygiene-${{ github.event.pull_request.number || github.run_id }}' - - - name: ๐ŸŽจ Run format check - uses: actions/github-script@v8 - env: - CHECK_MODE: "format" - with: - script: | - const { execSync } = require('child_process'); - execSync('cd .github/scripts && npm ci', { stdio: 'inherit' }); - const { default: runHygieneCheck } = await import('${{ github.workspace }}/.github/scripts/src/runHygieneCheckV2.ts'); - await runHygieneCheck(); - - - name: ๐Ÿ“ค Upload format result - uses: actions/upload-artifact@v7 - with: - name: format-result - path: artifacts/hygiene/format-result.json - retention-days: 7 - - # ============================================================================ - # LINT CHECK - # ============================================================================ - lint: - name: ๐Ÿ” Lint Check - runs-on: ubuntu-latest - timeout-minutes: 15 - needs: setup - if: needs.setup.outputs.has-changes == 'true' || github.event_name == 'workflow_dispatch' - continue-on-error: true - steps: - - name: ๐Ÿ›ซ Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 1 - ref: ${{ env.HEAD_REF }} - - - name: ๐Ÿ“ Display check context - run: | - echo "::group::๐Ÿ” Lint Check Context" - echo "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" - echo "โ”‚ ๐Ÿ” LINT CHECK โ”‚" - echo "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค" - echo "โ”‚ ๐Ÿ”ง Tool: ESLint (20+ plugins) โ”‚" - echo "โ”‚ ๐Ÿ“ Scope: TypeScript, JavaScript, React โ”‚" - echo "โ”‚ โš™๏ธ Config: Strict TypeScript mode โ”‚" - echo "โ”‚ ๐Ÿ“… Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" - echo "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" - echo "::endgroup::" - - - name: ๐Ÿ“ฆ Setup workspace - uses: ./.github/actions/setup-workspace - with: - node-version: ${{ env.NODE_VERSION }} - cache-key-prefix: 'hygiene-${{ github.event.pull_request.number || github.run_id }}' - - - name: ๐Ÿ” Run lint check - uses: actions/github-script@v8 - env: - CHECK_MODE: "lint" - with: - script: | - const { execSync } = require('child_process'); - execSync('cd .github/scripts && npm ci', { stdio: 'inherit' }); - const { default: runHygieneCheck } = await import('${{ github.workspace }}/.github/scripts/src/runHygieneCheckV2.ts'); - await runHygieneCheck(); - - - name: ๐Ÿ“ค Upload lint result - uses: actions/upload-artifact@v7 - with: - name: lint-result - path: artifacts/hygiene/lint-result.json - retention-days: 7 - - # ============================================================================ - # UNIT TESTS - # ============================================================================ - test: - name: ๐Ÿงช Unit Tests - runs-on: ubuntu-latest - timeout-minutes: 15 - needs: setup - if: needs.setup.outputs.has-changes == 'true' || github.event_name == 'workflow_dispatch' - continue-on-error: true - steps: - - name: ๐Ÿ›ซ Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 1 - ref: ${{ env.HEAD_REF }} - - - name: ๐Ÿ“ Display check context - run: | - echo "::group::๐Ÿ” Unit Tests Context" - echo "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" - echo "โ”‚ ๐Ÿงช UNIT TESTS โ”‚" - echo "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค" - echo "โ”‚ ๐Ÿ”ง Framework: Vitest โ”‚" - echo "โ”‚ ๐Ÿ“ Scope: All test files โ”‚" - echo "โ”‚ ๐Ÿ“Š Coverage: Enabled (target: 85%) โ”‚" - echo "โ”‚ ๐Ÿ“… Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" - echo "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" - echo "::endgroup::" - - - name: ๐Ÿ“ฆ Setup workspace - uses: ./.github/actions/setup-workspace - with: - node-version: ${{ env.NODE_VERSION }} - cache-key-prefix: 'hygiene-${{ github.event.pull_request.number || github.run_id }}' - - - name: ๐Ÿ”จ Build components package - run: npm run build:components - - - name: ๐Ÿงช Run test check - uses: actions/github-script@v8 - env: - CHECK_MODE: "test" - with: - script: | - const { execSync } = require('child_process'); - execSync('cd .github/scripts && npm ci', { stdio: 'inherit' }); - const { default: runHygieneCheck } = await import('${{ github.workspace }}/.github/scripts/src/runHygieneCheckV2.ts'); - await runHygieneCheck(); - - - name: ๐Ÿ“ค Upload test result - uses: actions/upload-artifact@v7 - with: - name: test-result - path: artifacts/hygiene/test-result.json - retention-days: 7 - - - name: ๐Ÿ“ค Upload test coverage & output - uses: actions/upload-artifact@v7 - if: always() - with: - name: test-coverage - path: | - artifacts/vitest-output.json - coverage/vitest/coverage-summary.json - retention-days: 7 - - # ============================================================================ - # CODE STATISTICS & BUNDLE ANALYSIS - # ============================================================================ - stats: - name: ๐Ÿ“Š Code Statistics - runs-on: ubuntu-latest - timeout-minutes: 12 - needs: setup - if: needs.setup.outputs.has-changes == 'true' || github.event_name == 'workflow_dispatch' - continue-on-error: true - steps: - - name: ๐Ÿ›ซ Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 0 - ref: ${{ env.HEAD_REF }} - - - name: ๐Ÿ“ Display check context - run: | - echo "::group::๐Ÿ” Statistics Context" - echo "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" - echo "โ”‚ ๐Ÿ“Š CODE STATISTICS โ”‚" - echo "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค" - echo "โ”‚ ๐Ÿ“ฆ Bundle Size: Analysis vs main branch โ”‚" - echo "โ”‚ ๐Ÿ“ Lines of Code: Tracked for regression โ”‚" - echo "โ”‚ ๐Ÿ“ˆ Complexity: Cyclomatic complexity metrics โ”‚" - echo "โ”‚ ๐Ÿ“… Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" - echo "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" - echo "::endgroup::" - - - name: ๐Ÿ“ฆ Setup workspace - uses: ./.github/actions/setup-workspace - with: - node-version: ${{ env.NODE_VERSION }} - cache-key-prefix: 'hygiene-${{ github.event.pull_request.number || github.run_id }}' - - - name: ๐Ÿ“Š Compute statistics - uses: actions/github-script@v8 - env: - CHECK_MODE: "stats" - with: - script: | - const { execSync } = require('child_process'); - execSync('cd .github/scripts && npm ci', { stdio: 'inherit' }); - const { default: runHygieneCheck } = await import('${{ github.workspace }}/.github/scripts/src/runHygieneCheckV2.ts'); - await runHygieneCheck(); - - - name: ๐Ÿ“ค Upload stats result - uses: actions/upload-artifact@v7 - with: - name: stats-result - path: artifacts/hygiene/stats-result.json - retention-days: 7 - - # ============================================================================ - # SUMMARY & PR COMMENT - # ============================================================================ - summary: - name: ๐Ÿ“‹ Summary & PR Comment - runs-on: ubuntu-latest - timeout-minutes: 5 - needs: [setup, format, lint, test, stats] - if: always() && (needs.setup.outputs.has-changes == 'true' || github.event_name == 'workflow_dispatch') - steps: - - name: ๐Ÿ›ซ Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 1 - - - name: ๐Ÿ“ Display summary context - run: | - echo "::group::๐Ÿ” Summary Context" - echo "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" - echo "โ”‚ ๐Ÿ“‹ SUMMARY & PR COMMENT โ”‚" - echo "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค" - echo "โ”‚ ๐ŸŽจ Format: ${{ needs.format.result }}" - echo "โ”‚ ๐Ÿ” Lint: ${{ needs.lint.result }}" - echo "โ”‚ ๐Ÿงช Tests: ${{ needs.test.result }}" - echo "โ”‚ ๐Ÿ“Š Stats: ${{ needs.stats.result }}" - echo "โ”‚ ๐Ÿ“… Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" - echo "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" - echo "::endgroup::" - - - name: ๐Ÿ“ฆ Setup workspace - uses: ./.github/actions/setup-workspace - with: - node-version: ${{ env.NODE_VERSION }} - cache-key-prefix: 'hygiene-${{ github.event.pull_request.number || github.run_id }}' - - - name: ๐Ÿ“ฅ Download all artifacts - uses: actions/download-artifact@v8 - with: - path: artifacts/hygiene - pattern: "*-result" - merge-multiple: true - - - name: ๐Ÿ“‹ Generate summary and post comment - uses: actions/github-script@v8 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - CHECK_MODE: "summary" - PR_NUMBER: ${{ github.event.pull_request.number }} - with: - script: | - const { execSync } = require('child_process'); - execSync('cd .github/scripts && npm ci', { stdio: 'inherit' }); - const { default: runHygieneCheck } = await import('${{ github.workspace }}/.github/scripts/src/runHygieneCheckV2.ts'); - await runHygieneCheck(); - - - name: ๐ŸŽฏ Determine final status - id: final-status - run: | - # Check if any of the check jobs failed (not continue-on-error failures) - FORMAT_STATUS="${{ needs.format.result }}" - LINT_STATUS="${{ needs.lint.result }}" - TEST_STATUS="${{ needs.test.result }}" - STATS_STATUS="${{ needs.stats.result }}" - - echo "Format: $FORMAT_STATUS" - echo "Lint: $LINT_STATUS" - echo "Test: $TEST_STATUS" - echo "Stats: $STATS_STATUS" - - # Determine if any check failed - if [[ "$FORMAT_STATUS" == "failure" || "$LINT_STATUS" == "failure" || "$TEST_STATUS" == "failure" ]]; then - echo "has-failures=true" >> $GITHUB_OUTPUT - else - echo "has-failures=false" >> $GITHUB_OUTPUT - fi - - # The actual pass/fail is determined by the artifacts - # This just logs the job-level results - echo "โœ… Summary job completed. See PR comment for detailed results." - - - name: ๐Ÿท๏ธ Label Dependabot PR on failure - if: steps.final-status.outputs.has-failures == 'true' && github.actor == 'dependabot[bot]' - uses: actions/github-script@v8 - with: - script: | - const prNumber = context.payload.pull_request?.number; - if (!prNumber) { - console.log('Not a PR context, skipping label'); - return; - } - - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.name, - issue_number: prNumber, - labels: ['breaking-change', 'needs-review'] - }); - - console.log(`Added 'breaking-change' and 'needs-review' labels to PR #${prNumber}`); +--- +# ============================================================================= +# Code Hygiene Check Workflow (v2) +# ============================================================================= +# This workflow performs comprehensive code quality checks on pull requests. +# +# Architecture: +# - Phase 1: Setup & Change Detection +# - Phase 2: Parallel Quality Checks (each uploads JSON artifact) +# - Format Check โ†’ format-result.json +# - Lint Check โ†’ lint-result.json +# - Unit Tests โ†’ test-result.json +# - Stats โ†’ stats-result.json +# - Phase 3: Summary (downloads all artifacts, posts rich PR comment) +# +# Data Flow: +# - Each check job produces a {check}-result.json artifact +# - Summary job downloads all artifacts in parallel +# - Comment builder consumes artifacts to generate rich PR comment +# - New comment created on each run (no update logic) +# +# Quality Gates: +# - Formatting: Must pass Prettier check +# - Linting: Must pass ESLint with strict TypeScript rules +# - Testing: All unit tests must pass +# - Bundle Size: Tracked for regression detection (vs main) +# +# Artifacts: +# - retention-days: 7 (for debugging past PR failures) +# - Pattern: artifacts/hygiene/{check}-result.json +# +# Local Resolution: +# - Format: npm run format +# - Lint: npm run lint +# - Tests: npm run test:unit +# ============================================================================= + +name: "Code Hygiene Check" + +permissions: + contents: read + pull-requests: write + +on: + pull_request: + types: [opened, synchronize, reopened] + + workflow_dispatch: + +concurrency: + group: hygiene-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + NODE_VERSION: "24" + HEAD_REF: ${{ github.event.pull_request.head.sha || github.sha }} + BASE_REF: ${{ github.event.pull_request.base.sha || 'origin/main' }} + +jobs: + # ============================================================================ + # SETUP & CHANGE DETECTION + # ============================================================================ + setup: + name: ๐Ÿงฉ Setup & Change Detection + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + has-changes: ${{ steps.detect.outputs.has-changes }} + changed-files-count: ${{ steps.detect.outputs.changed-files-count }} + steps: + - name: ๐Ÿ›ซ Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + ref: ${{ env.HEAD_REF }} + + - name: ๐Ÿ“ Display workflow context + run: | + echo "::group::๐Ÿ” Hygiene Check Context" + echo "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" + echo "โ”‚ ๐Ÿงน CODE HYGIENE CHECK PIPELINE โ”‚" + echo "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค" + echo "โ”‚ ๐Ÿ“ฆ Job: Setup & Change Detection โ”‚" + echo "โ”‚ ๐Ÿ”€ PR Number: ${{ github.event.pull_request.number || 'N/A' }}" + echo "โ”‚ ๐Ÿท๏ธ Head SHA: ${{ env.HEAD_REF }}" + echo "โ”‚ ๐Ÿท๏ธ Base SHA: ${{ env.BASE_REF }}" + echo "โ”‚ ๐Ÿ‘ค Author: ${{ github.event.pull_request.user.login || github.actor }}" + echo "โ”‚ ๐Ÿ“… Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" + echo "โ”‚ ๐Ÿ”— Run ID: ${{ github.run_id }}" + echo "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" + echo "::endgroup::" + + # Write initial summary + echo "## ๐Ÿงน Code Hygiene Check" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Parameter | Value |" >> $GITHUB_STEP_SUMMARY + echo "|-----------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| PR | #${{ github.event.pull_request.number || 'N/A' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Author | @${{ github.event.pull_request.user.login || github.actor }} |" >> $GITHUB_STEP_SUMMARY + echo "| Head SHA | \`${{ env.HEAD_REF }}\` |" >> $GITHUB_STEP_SUMMARY + + - name: ๐Ÿ“ฆ Setup workspace + uses: ./.github/actions/setup-workspace + with: + node-version: ${{ env.NODE_VERSION }} + cache-key-prefix: 'hygiene-${{ github.event.pull_request.number || github.run_id }}' + install-node-dependencies: 'true' + + - name: ๐Ÿ”Ž Detect changes + id: detect + uses: actions/github-script@v9 + env: + CHECK_MODE: "detect" + with: + script: | + const { execSync } = require('child_process'); + execSync('cd .github/scripts && npm ci', { stdio: 'inherit' }); + const { default: runHygieneCheck } = await import('${{ github.workspace }}/.github/scripts/src/runHygieneCheckV2.ts'); + await runHygieneCheck(); + + # ============================================================================ + # FORMAT CHECK + # ============================================================================ + format: + name: ๐ŸŽจ Format Check + runs-on: ubuntu-latest + timeout-minutes: 10 + needs: setup + if: needs.setup.outputs.has-changes == 'true' || github.event_name == 'workflow_dispatch' + continue-on-error: true + steps: + - name: ๐Ÿ›ซ Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 1 + ref: ${{ env.HEAD_REF }} + + - name: ๐Ÿ“ Display check context + run: | + echo "::group::๐Ÿ” Format Check Context" + echo "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" + echo "โ”‚ ๐ŸŽจ FORMAT CHECK โ”‚" + echo "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค" + echo "โ”‚ ๐Ÿ”ง Tool: Prettier โ”‚" + echo "โ”‚ ๐Ÿ“ Scope: All supported files โ”‚" + echo "โ”‚ ๐Ÿ”€ Changed files: ${{ needs.setup.outputs.changed-files-count || 'calculating...' }}" + echo "โ”‚ ๐Ÿ“… Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" + echo "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" + echo "::endgroup::" + + - name: ๐Ÿ“ฆ Setup workspace + uses: ./.github/actions/setup-workspace + with: + node-version: ${{ env.NODE_VERSION }} + cache-key-prefix: 'hygiene-${{ github.event.pull_request.number || github.run_id }}' + + - name: ๐ŸŽจ Run format check + uses: actions/github-script@v9 + env: + CHECK_MODE: "format" + with: + script: | + const { execSync } = require('child_process'); + execSync('cd .github/scripts && npm ci', { stdio: 'inherit' }); + const { default: runHygieneCheck } = await import('${{ github.workspace }}/.github/scripts/src/runHygieneCheckV2.ts'); + await runHygieneCheck(); + + - name: ๐Ÿ“ค Upload format result + uses: actions/upload-artifact@v7 + with: + name: format-result + path: artifacts/hygiene/format-result.json + retention-days: 7 + + # ============================================================================ + # LINT CHECK + # ============================================================================ + lint: + name: ๐Ÿ” Lint Check + runs-on: ubuntu-latest + timeout-minutes: 15 + needs: setup + if: needs.setup.outputs.has-changes == 'true' || github.event_name == 'workflow_dispatch' + continue-on-error: true + steps: + - name: ๐Ÿ›ซ Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 1 + ref: ${{ env.HEAD_REF }} + + - name: ๐Ÿ“ Display check context + run: | + echo "::group::๐Ÿ” Lint Check Context" + echo "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" + echo "โ”‚ ๐Ÿ” LINT CHECK โ”‚" + echo "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค" + echo "โ”‚ ๐Ÿ”ง Tool: ESLint (20+ plugins) โ”‚" + echo "โ”‚ ๐Ÿ“ Scope: TypeScript, JavaScript, React โ”‚" + echo "โ”‚ โš™๏ธ Config: Strict TypeScript mode โ”‚" + echo "โ”‚ ๐Ÿ“… Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" + echo "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" + echo "::endgroup::" + + - name: ๐Ÿ“ฆ Setup workspace + uses: ./.github/actions/setup-workspace + with: + node-version: ${{ env.NODE_VERSION }} + cache-key-prefix: 'hygiene-${{ github.event.pull_request.number || github.run_id }}' + + - name: ๐Ÿ” Run lint check + uses: actions/github-script@v9 + env: + CHECK_MODE: "lint" + with: + script: | + const { execSync } = require('child_process'); + execSync('cd .github/scripts && npm ci', { stdio: 'inherit' }); + const { default: runHygieneCheck } = await import('${{ github.workspace }}/.github/scripts/src/runHygieneCheckV2.ts'); + await runHygieneCheck(); + + - name: ๐Ÿ“ค Upload lint result + uses: actions/upload-artifact@v7 + with: + name: lint-result + path: artifacts/hygiene/lint-result.json + retention-days: 7 + + # ============================================================================ + # UNIT TESTS + # ============================================================================ + test: + name: ๐Ÿงช Unit Tests + runs-on: ubuntu-latest + timeout-minutes: 15 + needs: setup + if: needs.setup.outputs.has-changes == 'true' || github.event_name == 'workflow_dispatch' + continue-on-error: true + steps: + - name: ๐Ÿ›ซ Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 1 + ref: ${{ env.HEAD_REF }} + + - name: ๐Ÿ“ Display check context + run: | + echo "::group::๐Ÿ” Unit Tests Context" + echo "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" + echo "โ”‚ ๐Ÿงช UNIT TESTS โ”‚" + echo "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค" + echo "โ”‚ ๐Ÿ”ง Framework: Vitest โ”‚" + echo "โ”‚ ๐Ÿ“ Scope: All test files โ”‚" + echo "โ”‚ ๐Ÿ“Š Coverage: Enabled (target: 85%) โ”‚" + echo "โ”‚ ๐Ÿ“… Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" + echo "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" + echo "::endgroup::" + + - name: ๐Ÿ“ฆ Setup workspace + uses: ./.github/actions/setup-workspace + with: + node-version: ${{ env.NODE_VERSION }} + cache-key-prefix: 'hygiene-${{ github.event.pull_request.number || github.run_id }}' + + - name: ๐Ÿ”จ Build components package + run: npm run build:components + + - name: ๐Ÿงช Run test check + uses: actions/github-script@v9 + env: + CHECK_MODE: "test" + with: + script: | + const { execSync } = require('child_process'); + execSync('cd .github/scripts && npm ci', { stdio: 'inherit' }); + const { default: runHygieneCheck } = await import('${{ github.workspace }}/.github/scripts/src/runHygieneCheckV2.ts'); + await runHygieneCheck(); + + - name: ๐Ÿ“ค Upload test result + uses: actions/upload-artifact@v7 + with: + name: test-result + path: artifacts/hygiene/test-result.json + retention-days: 7 + + - name: ๐Ÿ“ค Upload test coverage & output + uses: actions/upload-artifact@v7 + if: always() + with: + name: test-coverage + path: | + artifacts/vitest-output.json + coverage/vitest/coverage-summary.json + retention-days: 7 + + # ============================================================================ + # CODE STATISTICS & BUNDLE ANALYSIS + # ============================================================================ + stats: + name: ๐Ÿ“Š Code Statistics + runs-on: ubuntu-latest + timeout-minutes: 12 + needs: setup + if: needs.setup.outputs.has-changes == 'true' || github.event_name == 'workflow_dispatch' + continue-on-error: true + steps: + - name: ๐Ÿ›ซ Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 0 + ref: ${{ env.HEAD_REF }} + + - name: ๐Ÿ“ Display check context + run: | + echo "::group::๐Ÿ” Statistics Context" + echo "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" + echo "โ”‚ ๐Ÿ“Š CODE STATISTICS โ”‚" + echo "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค" + echo "โ”‚ ๐Ÿ“ฆ Bundle Size: Analysis vs main branch โ”‚" + echo "โ”‚ ๐Ÿ“ Lines of Code: Tracked for regression โ”‚" + echo "โ”‚ ๐Ÿ“ˆ Complexity: Cyclomatic complexity metrics โ”‚" + echo "โ”‚ ๐Ÿ“… Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" + echo "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" + echo "::endgroup::" + + - name: ๐Ÿ“ฆ Setup workspace + uses: ./.github/actions/setup-workspace + with: + node-version: ${{ env.NODE_VERSION }} + cache-key-prefix: 'hygiene-${{ github.event.pull_request.number || github.run_id }}' + + - name: ๐Ÿ“Š Compute statistics + uses: actions/github-script@v9 + env: + CHECK_MODE: "stats" + with: + script: | + const { execSync } = require('child_process'); + execSync('cd .github/scripts && npm ci', { stdio: 'inherit' }); + const { default: runHygieneCheck } = await import('${{ github.workspace }}/.github/scripts/src/runHygieneCheckV2.ts'); + await runHygieneCheck(); + + - name: ๐Ÿ“ค Upload stats result + uses: actions/upload-artifact@v7 + with: + name: stats-result + path: artifacts/hygiene/stats-result.json + retention-days: 7 + + # ============================================================================ + # SUMMARY & PR COMMENT + # ============================================================================ + summary: + name: ๐Ÿ“‹ Summary & PR Comment + runs-on: ubuntu-latest + timeout-minutes: 5 + needs: [setup, format, lint, test, stats] + if: always() && (needs.setup.outputs.has-changes == 'true' || github.event_name == 'workflow_dispatch') + steps: + - name: ๐Ÿ›ซ Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 1 + + - name: ๐Ÿ“ Display summary context + run: | + echo "::group::๐Ÿ” Summary Context" + echo "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" + echo "โ”‚ ๐Ÿ“‹ SUMMARY & PR COMMENT โ”‚" + echo "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค" + echo "โ”‚ ๐ŸŽจ Format: ${{ needs.format.result }}" + echo "โ”‚ ๐Ÿ” Lint: ${{ needs.lint.result }}" + echo "โ”‚ ๐Ÿงช Tests: ${{ needs.test.result }}" + echo "โ”‚ ๐Ÿ“Š Stats: ${{ needs.stats.result }}" + echo "โ”‚ ๐Ÿ“… Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" + echo "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" + echo "::endgroup::" + + - name: ๐Ÿ“ฆ Setup workspace + uses: ./.github/actions/setup-workspace + with: + node-version: ${{ env.NODE_VERSION }} + cache-key-prefix: 'hygiene-${{ github.event.pull_request.number || github.run_id }}' + + - name: ๐Ÿ“ฅ Download all artifacts + uses: actions/download-artifact@v8 + with: + path: artifacts/hygiene + pattern: "*-result" + merge-multiple: true + + - name: ๐Ÿ“‹ Generate summary and post comment + uses: actions/github-script@v9 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CHECK_MODE: "summary" + PR_NUMBER: ${{ github.event.pull_request.number }} + with: + script: | + const { execSync } = require('child_process'); + execSync('cd .github/scripts && npm ci', { stdio: 'inherit' }); + const { default: runHygieneCheck } = await import('${{ github.workspace }}/.github/scripts/src/runHygieneCheckV2.ts'); + await runHygieneCheck(); + + - name: ๐ŸŽฏ Determine final status + id: final-status + run: | + # Check if any of the check jobs failed (not continue-on-error failures) + FORMAT_STATUS="${{ needs.format.result }}" + LINT_STATUS="${{ needs.lint.result }}" + TEST_STATUS="${{ needs.test.result }}" + STATS_STATUS="${{ needs.stats.result }}" + + echo "Format: $FORMAT_STATUS" + echo "Lint: $LINT_STATUS" + echo "Test: $TEST_STATUS" + echo "Stats: $STATS_STATUS" + + # Determine if any check failed + if [[ "$FORMAT_STATUS" == "failure" || "$LINT_STATUS" == "failure" || "$TEST_STATUS" == "failure" ]]; then + echo "has-failures=true" >> $GITHUB_OUTPUT + else + echo "has-failures=false" >> $GITHUB_OUTPUT + fi + + # The actual pass/fail is determined by the artifacts + # This just logs the job-level results + echo "โœ… Summary job completed. See PR comment for detailed results." + + - name: ๐Ÿท๏ธ Label Dependabot PR on failure + if: steps.final-status.outputs.has-failures == 'true' && github.actor == 'dependabot[bot]' + uses: actions/github-script@v9 + with: + script: | + const prNumber = context.payload.pull_request?.number; + if (!prNumber) { + console.log('Not a PR context, skipping label'); + return; + } + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.name, + issue_number: prNumber, + labels: ['breaking-change', 'needs-review'] + }); + + console.log(`Added 'breaking-change' and 'needs-review' labels to PR #${prNumber}`); diff --git a/.github/workflows/official-website-build.yml b/.github/workflows/official-website-build.yml index 0c8270072..198965704 100644 --- a/.github/workflows/official-website-build.yml +++ b/.github/workflows/official-website-build.yml @@ -1,370 +1,370 @@ -# ============================================================================= -# Main Website Build Workflow (Development/Preview) -# ============================================================================= -# This workflow handles the CI/CD pipeline for the main Next.js website -# (arolariu.ro) in development/preview environments, including testing, -# containerization, and deployment to development Azure App Service. -# -# Architecture: -# - Phase 1: Test - Unit tests (Vitest) and E2E tests (Playwright) -# - Phase 2: Build - Containerizes Next.js app and pushes to ACR -# -# Technology Stack: -# - Next.js 16.1.6 (App Router, React Server Components) -# - React 19.2.4 with TypeScript 5.9.3 (strict mode) -# - Playwright for E2E testing -# - Vitest for unit testing -# - Docker for containerization -# -# Triggers: -# - Automatic: Push to 'preview' branch with changes in sites/arolariu.ro/ -# - Manual: workflow_dispatch with environment/infrastructure selection -# -# Test Coverage: -# - Unit tests with code coverage (uploaded to CodeCov) -# - E2E tests with Playwright -# - Test reports uploaded as artifacts -# - PR comments with test results (on preview branch) -# -# Security: -# - Uses OIDC for Azure authentication (no long-lived secrets) -# - Azure Container Registry credentials in GitHub Secrets -# - Environment-specific configurations -# -# Prerequisites: -# 1. Azure Managed Identity configured with Federated Credentials -# 2. Azure Container Registry with push/pull access -# 3. GitHub Secrets configured: -# - AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID -# - AZURE_FRONTEND_MANAGED_IDENTITY_CLIENT_ID -# - AZURE_CONTAINER_REGISTRY_ADDRESS -# - CODECOV_TOKEN (for coverage reporting) -# -# Related Workflows: -# - official-website-release.yml: Deploys built container to Azure -# ============================================================================= - -name: "official-website-build" - -permissions: - id-token: write # Required for OIDC authentication with Azure - contents: read # Required to checkout repository - issues: write # Required for PR comments on test results - pull-requests: write # Required for PR comments on test results - -defaults: - run: - working-directory: ./sites/arolariu.ro - -on: - # Automatic trigger on preview branch changes to main website - push: - branches: ["preview"] - paths: ["sites/arolariu.ro/**"] - - # Manual trigger for controlled builds - workflow_dispatch: - inputs: - environment: - description: "The environment to deploy to. (e.g. 'development', 'production')" - options: - - "development" - - "production" - default: "development" - required: true - type: choice - infrastructure: - description: "The infrastructure where it will be deployed (e.g. 'local' or 'azure')" - options: - - 'local' - - 'azure' - default: "azure" - required: true - type: choice - container_name: - description: "The name of the container to build and deploy. (e.g. 'frontend/dev.arolariu', 'frontend/arolariu')" - options: - - "frontend/dev.arolariu" - - "frontend/arolariu" - default: "frontend/dev.arolariu" - required: true - type: choice - skip_tests: - description: "Skip the test phase? (true/false)" - type: boolean - default: false - required: false - -env: - COMMIT_SHA: ${{ github.sha }} - PRODUCTION: ${{ inputs.environment == 'production' && 'true' || 'false' }} - INFRA: ${{ inputs.infrastructure || 'azure' }} - AZURE_TENANT_ID: ${{secrets.AZURE_TENANT_ID}} - AZURE_SUBSCRIPTION_ID: ${{secrets.AZURE_SUBSCRIPTION_ID}} - AZURE_CLIENT_ID: ${{secrets.AZURE_FRONTEND_MANAGED_IDENTITY_CLIENT_ID}} - # Build-time injections: inlined by next.config.ts env block (SITE_*) - # These must be present when `npm run build` executes inside the Docker builder stage. - SITE_ENV: ${{ inputs.environment == 'production' && 'PRODUCTION' || 'DEVELOPMENT' }} - SITE_NAME: ${{ inputs.environment == 'production' && 'arolariu.ro' || 'dev.arolariu.ro' }} - SITE_URL: ${{ inputs.environment == 'production' && 'https://arolariu.ro' || 'https://dev.arolariu.ro' }} - -jobs: - test: - if: github.repository == 'arolariu/arolariu.ro' && inputs.skip_tests != true - name: ๐Ÿงช Test phase (1/2) - runs-on: ubuntu-latest - environment: ${{ inputs.environment || 'development'}} - timeout-minutes: 10 - steps: - - name: ๐Ÿ›ซ Checking out the branch inside the runner environment... - uses: actions/checkout@v6 - - - name: ๐Ÿ“ Display workflow context - run: | - echo "::group::๐Ÿ” Workflow Context" - echo "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" - echo "โ”‚ ๐ŸŒ WEBSITE CI/CD PIPELINE โ”‚" - echo "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค" - echo "โ”‚ ๐Ÿ“ฆ Job: Test phase (1/2) โ”‚" - echo "โ”‚ ๐ŸŒ Environment: ${{ inputs.environment || 'development' }}" - echo "โ”‚ ๐Ÿ”ง Infrastructure: ${{ env.INFRA }}" - echo "โ”‚ ๐Ÿท๏ธ Commit SHA: ${{ github.sha }}" - echo "โ”‚ ๐Ÿ‘ค Triggered by: ${{ github.actor }}" - echo "โ”‚ ๐Ÿ“… Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" - echo "โ”‚ ๐Ÿ”— Run ID: ${{ github.run_id }}" - echo "โ”‚ ๐Ÿ”ข Run Number: ${{ github.run_number }}" - echo "โ”‚ ๐Ÿงช Skip Tests: ${{ inputs.skip_tests || 'false' }}" - echo "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" - echo "::endgroup::" - - - name: ๐Ÿ”’ Performing auth against Azure Public Cloud... - uses: Azure/login@v3 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - - name: ๐Ÿ“ฆ Setup workspace - uses: ./.github/actions/setup-workspace - with: - node-version: '24' - playwright: 'true' - generate: 'true' - cache-key-prefix: 'website' - - - name: ๐Ÿงช Running unit tests... - working-directory: './sites/arolariu.ro' - run: | - echo "::group::๐Ÿงช Running Vitest Unit Tests" - echo "๐Ÿ“ Framework: Vitest" - echo "๐Ÿ“ Coverage: Enabled" - echo "" - START_TIME=$(date +%s) - npm run test:unit - END_TIME=$(date +%s) - DURATION=$((END_TIME - START_TIME)) - echo "" - echo "::notice::โœ… Unit tests completed in ${DURATION}s" - echo "::endgroup::" - - echo "## ๐Ÿงช Unit Test Results" >> $GITHUB_STEP_SUMMARY - echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY - echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY - echo "| Duration | ${DURATION}s |" >> $GITHUB_STEP_SUMMARY - echo "| Framework | Vitest |" >> $GITHUB_STEP_SUMMARY - env: # We will use local assets for the tests - USE_CDN: "false" - - - name: ๐Ÿงช Running E2E tests... - working-directory: './sites/arolariu.ro' - run: | - echo "::group::๐ŸŽญ Running Playwright E2E Tests" - echo "๐Ÿ“ Framework: Playwright" - echo "๐Ÿ“ Browsers: Chromium only" - echo "" - START_TIME=$(date +%s) - npm run test:e2e - END_TIME=$(date +%s) - DURATION=$((END_TIME - START_TIME)) - echo "" - echo "::notice::โœ… E2E tests completed in ${DURATION}s" - echo "::endgroup::" - - echo "## ๐ŸŽญ E2E Test Results" >> $GITHUB_STEP_SUMMARY - echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY - echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY - echo "| Duration | ${DURATION}s |" >> $GITHUB_STEP_SUMMARY - echo "| Framework | Playwright |" >> $GITHUB_STEP_SUMMARY - env: # We will use local assets for the tests - USE_CDN: "false" - - - name: ๐Ÿ“ค Upload Test Reports - if: always() - uses: actions/upload-artifact@v7 - with: - name: test-report-${{ inputs.environment || 'development' }}-${{ github.run_id }} - path: sites/arolariu.ro/code-cov/ - if-no-files-found: error - retention-days: 7 - - - name: ๐Ÿ’ฌ Create PR Comment - uses: actions/github-script@v8 - if: github.event_name == 'push' && github.ref == 'refs/heads/preview' - env: - PR_NUMBER: ${{ github.event.pull_request.number }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - JOB_STATUS: ${{ job.status }} - COMMIT_SHA: ${{ github.sha }} - RUN_ID: ${{ github.run_id }} - BRANCH_NAME: ${{ github.ref_name }} - GITHUB_WORKSPACE: ${{ github.workspace }} - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - // Install dependencies required by the script - const { execSync } = require('child_process'); - execSync('cd .github/scripts && npm ci', { stdio: 'inherit' }); - - // Import and execute the unified unit test action script - const { default: runUnitTestAction } = await import('${{ github.workspace }}/.github/scripts/src/runUnitTestAction.ts'); - await runUnitTestAction(); - - - name: โ˜‚๏ธ Uploading code coverage report to CodeCov - uses: codecov/codecov-action@v6 - with: - token: ${{ secrets.CODECOV_TOKEN }} - directory: "sites/arolariu.ro/code-cov" - - build: - if: "!cancelled() && github.repository == 'arolariu/arolariu.ro' && (needs.test.result == 'success' || needs.test.result == 'skipped')" - name: โš’๏ธ Build phase (2/2) - needs: test - runs-on: ubuntu-latest - environment: ${{ inputs.environment || 'development'}} - steps: - - name: ๐Ÿ›ซ Checking out the branch inside the runner environment... - uses: actions/checkout@v6 - - - name: ๐Ÿ“‹ Display build context - run: | - echo "::group::๐Ÿ” Build Context" - echo "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" - echo "โ”‚ ๐Ÿ—๏ธ WEBSITE BUILD PHASE โ”‚" - echo "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค" - echo "โ”‚ ๐Ÿ“ฆ Job: Build phase (2/2) โ”‚" - echo "โ”‚ ๐Ÿณ Container: ${{ inputs.container_name || 'frontend/dev.arolariu' }}" - echo "โ”‚ ๐Ÿท๏ธ Tag: ${{ inputs.environment == 'production' && github.sha || 'latest' }}" - echo "โ”‚ ๐ŸŒ Environment: ${{ inputs.environment || 'development' }}" - echo "โ”‚ ๐Ÿ“… Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" - echo "โ”‚ ๐Ÿงช Tests: ${{ needs.test.result }}" - echo "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" - echo "::endgroup::" - - - name: โš ๏ธ Test Skip Warning - if: inputs.skip_tests == true - run: | - echo "::warning::Tests were skipped for this build (run ${{ github.run_id }})" - echo "::warning::Environment: ${{ inputs.environment || 'development' }}" - echo "::warning::Triggered by: ${{ github.actor }}" - - - name: ๐Ÿ”’ Performing auth against Azure Public Cloud... - uses: Azure/login@v3 - with: - client-id: ${{ secrets.AZURE_CLIENT_ID }} - tenant-id: ${{ secrets.AZURE_TENANT_ID }} - subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} - - - name: ๐Ÿ“ฆ Setup workspace - uses: ./.github/actions/setup-workspace - with: - node-version: '24' - generate: 'true' - cache-key-prefix: 'website' - - - name: ๐Ÿ”’ Performing auth against private Azure Container Registry... - working-directory: '.' - run: az acr login --name "${ACR_LOGIN_SERVER%%.*}" - env: - ACR_LOGIN_SERVER: ${{ secrets.AZURE_CONTAINER_REGISTRY_ADDRESS }} - - - name: ๐Ÿ“‹ Verify generated .env file exists - run: | - if [ -f "sites/arolariu.ro/.env" ]; then - echo "โœ… .env file found" - else - echo "โŒ .env file not found - build will likely fail" - exit 1 - fi - shell: bash - working-directory: '.' - - - name: ๐Ÿ—๏ธ Building the docker container... - working-directory: '.' - env: - CONTAINER_NAME: ${{inputs.container_name || 'frontend/dev.arolariu'}} - CONTAINER_TAG: ${{inputs.environment == 'production' && github.sha || 'latest'}} - run: | - echo "::group::๐Ÿณ Docker Build" - echo "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" - echo "โ”‚ ๐Ÿณ DOCKER BUILD STARTED โ”‚" - echo "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค" - echo "โ”‚ ๐Ÿ“ฆ Image: ${{ secrets.AZURE_CONTAINER_REGISTRY_ADDRESS }}/${{env.CONTAINER_NAME}}:${{env.CONTAINER_TAG}}" - echo "โ”‚ ๐Ÿ“ Dockerfile: infra/containers/Dockerfile.frontend" - echo "โ”‚ ๐Ÿ”ง Build args:" - echo "โ”‚ - COMMIT_SHA=${{ env.COMMIT_SHA }}" - echo "โ”‚ - INFRA=${{ env.INFRA }}" - echo "โ”‚ - SITE_ENV=${{ env.SITE_ENV }}" - echo "โ”‚ - SITE_NAME=${{ env.SITE_NAME }}" - echo "โ”‚ - SITE_URL=${{ env.SITE_URL }}" - echo "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" - echo "" - - START_TIME=$(date +%s) - docker build -f infra/containers/Dockerfile.frontend \ - --build-arg AZURE_TENANT_ID=${{ env.AZURE_TENANT_ID }} \ - --build-arg AZURE_SUBSCRIPTION_ID=${{ env.AZURE_SUBSCRIPTION_ID }} \ - --build-arg AZURE_CLIENT_ID=${{ env.AZURE_CLIENT_ID }} \ - --build-arg COMMIT_SHA=${{ env.COMMIT_SHA }} \ - --build-arg INFRA=${{ env.INFRA }} \ - --build-arg SITE_ENV=${{ env.SITE_ENV }} \ - --build-arg SITE_URL=${{ env.SITE_URL }} \ - --build-arg SITE_NAME=${{ env.SITE_NAME }} \ - -t ${{ secrets.AZURE_CONTAINER_REGISTRY_ADDRESS }}/${{env.CONTAINER_NAME}}:${{env.CONTAINER_TAG}} . - END_TIME=$(date +%s) - DURATION=$((END_TIME - START_TIME)) - echo "" - echo "::notice::โœ… Docker build completed in ${DURATION}s" - echo "::endgroup::" - - # Get image size - IMAGE_SIZE=$(docker images --format "{{.Size}}" ${{ secrets.AZURE_CONTAINER_REGISTRY_ADDRESS }}/${{env.CONTAINER_NAME}}:${{env.CONTAINER_TAG}}) - echo "๐Ÿ“ฆ Image size: $IMAGE_SIZE" - - # Write to job summary - echo "## ๐Ÿณ Docker Build Results" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY - echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY - echo "| Build Duration | ${DURATION}s |" >> $GITHUB_STEP_SUMMARY - echo "| Image Size | ${IMAGE_SIZE} |" >> $GITHUB_STEP_SUMMARY - echo "| Container | \`${{env.CONTAINER_NAME}}:${{env.CONTAINER_TAG}}\` |" >> $GITHUB_STEP_SUMMARY - - - name: โซ Pushing the docker container... - env: - CONTAINER_NAME: ${{inputs.container_name || 'frontend/dev.arolariu'}} - CONTAINER_TAG: ${{inputs.environment == 'production' && github.sha || 'latest'}} - run: | - echo "::group::โซ Docker Push" - echo "๐Ÿ“ค Pushing to Azure Container Registry..." - START_TIME=$(date +%s) - docker push ${{ secrets.AZURE_CONTAINER_REGISTRY_ADDRESS }}/${{env.CONTAINER_NAME}}:${{env.CONTAINER_TAG}} - END_TIME=$(date +%s) - DURATION=$((END_TIME - START_TIME)) - echo "::notice::โœ… Docker push completed in ${DURATION}s" - echo "::endgroup::" - - echo "## โซ Docker Push Results" >> $GITHUB_STEP_SUMMARY - echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY - echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY - echo "| Push Duration | ${DURATION}s |" >> $GITHUB_STEP_SUMMARY - echo "| Registry | Azure Container Registry |" >> $GITHUB_STEP_SUMMARY +# ============================================================================= +# Main Website Build Workflow (Development/Preview) +# ============================================================================= +# This workflow handles the CI/CD pipeline for the main Next.js website +# (arolariu.ro) in development/preview environments, including testing, +# containerization, and deployment to development Azure App Service. +# +# Architecture: +# - Phase 1: Test - Unit tests (Vitest) and E2E tests (Playwright) +# - Phase 2: Build - Containerizes Next.js app and pushes to ACR +# +# Technology Stack: +# - Next.js 16.1.6 (App Router, React Server Components) +# - React 19.2.4 with TypeScript 5.9.3 (strict mode) +# - Playwright for E2E testing +# - Vitest for unit testing +# - Docker for containerization +# +# Triggers: +# - Automatic: Push to 'preview' branch with changes in sites/arolariu.ro/ +# - Manual: workflow_dispatch with environment/infrastructure selection +# +# Test Coverage: +# - Unit tests with code coverage (uploaded to CodeCov) +# - E2E tests with Playwright +# - Test reports uploaded as artifacts +# - PR comments with test results (on preview branch) +# +# Security: +# - Uses OIDC for Azure authentication (no long-lived secrets) +# - Azure Container Registry credentials in GitHub Secrets +# - Environment-specific configurations +# +# Prerequisites: +# 1. Azure Managed Identity configured with Federated Credentials +# 2. Azure Container Registry with push/pull access +# 3. GitHub Secrets configured: +# - AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID +# - AZURE_FRONTEND_MANAGED_IDENTITY_CLIENT_ID +# - AZURE_CONTAINER_REGISTRY_ADDRESS +# - CODECOV_TOKEN (for coverage reporting) +# +# Related Workflows: +# - official-website-release.yml: Deploys built container to Azure +# ============================================================================= + +name: "official-website-build" + +permissions: + id-token: write # Required for OIDC authentication with Azure + contents: read # Required to checkout repository + issues: write # Required for PR comments on test results + pull-requests: write # Required for PR comments on test results + +defaults: + run: + working-directory: ./sites/arolariu.ro + +on: + # Automatic trigger on preview branch changes to main website + push: + branches: ["preview"] + paths: ["sites/arolariu.ro/**"] + + # Manual trigger for controlled builds + workflow_dispatch: + inputs: + environment: + description: "The environment to deploy to. (e.g. 'development', 'production')" + options: + - "development" + - "production" + default: "development" + required: true + type: choice + infrastructure: + description: "The infrastructure where it will be deployed (e.g. 'local' or 'azure')" + options: + - 'local' + - 'azure' + default: "azure" + required: true + type: choice + container_name: + description: "The name of the container to build and deploy. (e.g. 'frontend/dev.arolariu', 'frontend/arolariu')" + options: + - "frontend/dev.arolariu" + - "frontend/arolariu" + default: "frontend/dev.arolariu" + required: true + type: choice + skip_tests: + description: "Skip the test phase? (true/false)" + type: boolean + default: false + required: false + +env: + COMMIT_SHA: ${{ github.sha }} + PRODUCTION: ${{ inputs.environment == 'production' && 'true' || 'false' }} + INFRA: ${{ inputs.infrastructure || 'azure' }} + AZURE_TENANT_ID: ${{secrets.AZURE_TENANT_ID}} + AZURE_SUBSCRIPTION_ID: ${{secrets.AZURE_SUBSCRIPTION_ID}} + AZURE_CLIENT_ID: ${{secrets.AZURE_FRONTEND_MANAGED_IDENTITY_CLIENT_ID}} + # Build-time injections: inlined by next.config.ts env block (SITE_*) + # These must be present when `npm run build` executes inside the Docker builder stage. + SITE_ENV: ${{ inputs.environment == 'production' && 'PRODUCTION' || 'DEVELOPMENT' }} + SITE_NAME: ${{ inputs.environment == 'production' && 'arolariu.ro' || 'dev.arolariu.ro' }} + SITE_URL: ${{ inputs.environment == 'production' && 'https://arolariu.ro' || 'https://dev.arolariu.ro' }} + +jobs: + test: + if: github.repository == 'arolariu/arolariu.ro' && inputs.skip_tests != true + name: ๐Ÿงช Test phase (1/2) + runs-on: ubuntu-latest + environment: ${{ inputs.environment || 'development'}} + timeout-minutes: 10 + steps: + - name: ๐Ÿ›ซ Checking out the branch inside the runner environment... + uses: actions/checkout@v6 + + - name: ๐Ÿ“ Display workflow context + run: | + echo "::group::๐Ÿ” Workflow Context" + echo "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" + echo "โ”‚ ๐ŸŒ WEBSITE CI/CD PIPELINE โ”‚" + echo "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค" + echo "โ”‚ ๐Ÿ“ฆ Job: Test phase (1/2) โ”‚" + echo "โ”‚ ๐ŸŒ Environment: ${{ inputs.environment || 'development' }}" + echo "โ”‚ ๐Ÿ”ง Infrastructure: ${{ env.INFRA }}" + echo "โ”‚ ๐Ÿท๏ธ Commit SHA: ${{ github.sha }}" + echo "โ”‚ ๐Ÿ‘ค Triggered by: ${{ github.actor }}" + echo "โ”‚ ๐Ÿ“… Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" + echo "โ”‚ ๐Ÿ”— Run ID: ${{ github.run_id }}" + echo "โ”‚ ๐Ÿ”ข Run Number: ${{ github.run_number }}" + echo "โ”‚ ๐Ÿงช Skip Tests: ${{ inputs.skip_tests || 'false' }}" + echo "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" + echo "::endgroup::" + + - name: ๐Ÿ”’ Performing auth against Azure Public Cloud... + uses: Azure/login@v3 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: ๐Ÿ“ฆ Setup workspace + uses: ./.github/actions/setup-workspace + with: + node-version: '24' + playwright: 'true' + generate: 'true' + cache-key-prefix: 'website' + + - name: ๐Ÿงช Running unit tests... + working-directory: './sites/arolariu.ro' + run: | + echo "::group::๐Ÿงช Running Vitest Unit Tests" + echo "๐Ÿ“ Framework: Vitest" + echo "๐Ÿ“ Coverage: Enabled" + echo "" + START_TIME=$(date +%s) + npm run test:unit + END_TIME=$(date +%s) + DURATION=$((END_TIME - START_TIME)) + echo "" + echo "::notice::โœ… Unit tests completed in ${DURATION}s" + echo "::endgroup::" + + echo "## ๐Ÿงช Unit Test Results" >> $GITHUB_STEP_SUMMARY + echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY + echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Duration | ${DURATION}s |" >> $GITHUB_STEP_SUMMARY + echo "| Framework | Vitest |" >> $GITHUB_STEP_SUMMARY + env: # We will use local assets for the tests + USE_CDN: "false" + + - name: ๐Ÿงช Running E2E tests... + working-directory: './sites/arolariu.ro' + run: | + echo "::group::๐ŸŽญ Running Playwright E2E Tests" + echo "๐Ÿ“ Framework: Playwright" + echo "๐Ÿ“ Browsers: Chromium only" + echo "" + START_TIME=$(date +%s) + npm run test:e2e + END_TIME=$(date +%s) + DURATION=$((END_TIME - START_TIME)) + echo "" + echo "::notice::โœ… E2E tests completed in ${DURATION}s" + echo "::endgroup::" + + echo "## ๐ŸŽญ E2E Test Results" >> $GITHUB_STEP_SUMMARY + echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY + echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Duration | ${DURATION}s |" >> $GITHUB_STEP_SUMMARY + echo "| Framework | Playwright |" >> $GITHUB_STEP_SUMMARY + env: # We will use local assets for the tests + USE_CDN: "false" + + - name: ๐Ÿ“ค Upload Test Reports + if: always() + uses: actions/upload-artifact@v7 + with: + name: test-report-${{ inputs.environment || 'development' }}-${{ github.run_id }} + path: sites/arolariu.ro/code-cov/ + if-no-files-found: error + retention-days: 7 + + - name: ๐Ÿ’ฌ Create PR Comment + uses: actions/github-script@v9 + if: github.event_name == 'push' && github.ref == 'refs/heads/preview' + env: + PR_NUMBER: ${{ github.event.pull_request.number }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + JOB_STATUS: ${{ job.status }} + COMMIT_SHA: ${{ github.sha }} + RUN_ID: ${{ github.run_id }} + BRANCH_NAME: ${{ github.ref_name }} + GITHUB_WORKSPACE: ${{ github.workspace }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + // Install dependencies required by the script + const { execSync } = require('child_process'); + execSync('cd .github/scripts && npm ci', { stdio: 'inherit' }); + + // Import and execute the unified unit test action script + const { default: runUnitTestAction } = await import('${{ github.workspace }}/.github/scripts/src/runUnitTestAction.ts'); + await runUnitTestAction(); + + - name: โ˜‚๏ธ Uploading code coverage report to CodeCov + uses: codecov/codecov-action@v6 + with: + token: ${{ secrets.CODECOV_TOKEN }} + directory: "sites/arolariu.ro/code-cov" + + build: + if: "!cancelled() && github.repository == 'arolariu/arolariu.ro' && (needs.test.result == 'success' || needs.test.result == 'skipped')" + name: โš’๏ธ Build phase (2/2) + needs: test + runs-on: ubuntu-latest + environment: ${{ inputs.environment || 'development'}} + steps: + - name: ๐Ÿ›ซ Checking out the branch inside the runner environment... + uses: actions/checkout@v6 + + - name: ๐Ÿ“‹ Display build context + run: | + echo "::group::๐Ÿ” Build Context" + echo "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" + echo "โ”‚ ๐Ÿ—๏ธ WEBSITE BUILD PHASE โ”‚" + echo "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค" + echo "โ”‚ ๐Ÿ“ฆ Job: Build phase (2/2) โ”‚" + echo "โ”‚ ๐Ÿณ Container: ${{ inputs.container_name || 'frontend/dev.arolariu' }}" + echo "โ”‚ ๐Ÿท๏ธ Tag: ${{ inputs.environment == 'production' && github.sha || 'latest' }}" + echo "โ”‚ ๐ŸŒ Environment: ${{ inputs.environment || 'development' }}" + echo "โ”‚ ๐Ÿ“… Timestamp: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" + echo "โ”‚ ๐Ÿงช Tests: ${{ needs.test.result }}" + echo "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" + echo "::endgroup::" + + - name: โš ๏ธ Test Skip Warning + if: inputs.skip_tests == true + run: | + echo "::warning::Tests were skipped for this build (run ${{ github.run_id }})" + echo "::warning::Environment: ${{ inputs.environment || 'development' }}" + echo "::warning::Triggered by: ${{ github.actor }}" + + - name: ๐Ÿ”’ Performing auth against Azure Public Cloud... + uses: Azure/login@v3 + with: + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + + - name: ๐Ÿ“ฆ Setup workspace + uses: ./.github/actions/setup-workspace + with: + node-version: '24' + generate: 'true' + cache-key-prefix: 'website' + + - name: ๐Ÿ”’ Performing auth against private Azure Container Registry... + working-directory: '.' + run: az acr login --name "${ACR_LOGIN_SERVER%%.*}" + env: + ACR_LOGIN_SERVER: ${{ secrets.AZURE_CONTAINER_REGISTRY_ADDRESS }} + + - name: ๐Ÿ“‹ Verify generated .env file exists + run: | + if [ -f "sites/arolariu.ro/.env" ]; then + echo "โœ… .env file found" + else + echo "โŒ .env file not found - build will likely fail" + exit 1 + fi + shell: bash + working-directory: '.' + + - name: ๐Ÿ—๏ธ Building the docker container... + working-directory: '.' + env: + CONTAINER_NAME: ${{inputs.container_name || 'frontend/dev.arolariu'}} + CONTAINER_TAG: ${{inputs.environment == 'production' && github.sha || 'latest'}} + run: | + echo "::group::๐Ÿณ Docker Build" + echo "โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”" + echo "โ”‚ ๐Ÿณ DOCKER BUILD STARTED โ”‚" + echo "โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค" + echo "โ”‚ ๐Ÿ“ฆ Image: ${{ secrets.AZURE_CONTAINER_REGISTRY_ADDRESS }}/${{env.CONTAINER_NAME}}:${{env.CONTAINER_TAG}}" + echo "โ”‚ ๐Ÿ“ Dockerfile: infra/containers/Dockerfile.frontend" + echo "โ”‚ ๐Ÿ”ง Build args:" + echo "โ”‚ - COMMIT_SHA=${{ env.COMMIT_SHA }}" + echo "โ”‚ - INFRA=${{ env.INFRA }}" + echo "โ”‚ - SITE_ENV=${{ env.SITE_ENV }}" + echo "โ”‚ - SITE_NAME=${{ env.SITE_NAME }}" + echo "โ”‚ - SITE_URL=${{ env.SITE_URL }}" + echo "โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜" + echo "" + + START_TIME=$(date +%s) + docker build -f infra/containers/Dockerfile.frontend \ + --build-arg AZURE_TENANT_ID=${{ env.AZURE_TENANT_ID }} \ + --build-arg AZURE_SUBSCRIPTION_ID=${{ env.AZURE_SUBSCRIPTION_ID }} \ + --build-arg AZURE_CLIENT_ID=${{ env.AZURE_CLIENT_ID }} \ + --build-arg COMMIT_SHA=${{ env.COMMIT_SHA }} \ + --build-arg INFRA=${{ env.INFRA }} \ + --build-arg SITE_ENV=${{ env.SITE_ENV }} \ + --build-arg SITE_URL=${{ env.SITE_URL }} \ + --build-arg SITE_NAME=${{ env.SITE_NAME }} \ + -t ${{ secrets.AZURE_CONTAINER_REGISTRY_ADDRESS }}/${{env.CONTAINER_NAME}}:${{env.CONTAINER_TAG}} . + END_TIME=$(date +%s) + DURATION=$((END_TIME - START_TIME)) + echo "" + echo "::notice::โœ… Docker build completed in ${DURATION}s" + echo "::endgroup::" + + # Get image size + IMAGE_SIZE=$(docker images --format "{{.Size}}" ${{ secrets.AZURE_CONTAINER_REGISTRY_ADDRESS }}/${{env.CONTAINER_NAME}}:${{env.CONTAINER_TAG}}) + echo "๐Ÿ“ฆ Image size: $IMAGE_SIZE" + + # Write to job summary + echo "## ๐Ÿณ Docker Build Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY + echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Build Duration | ${DURATION}s |" >> $GITHUB_STEP_SUMMARY + echo "| Image Size | ${IMAGE_SIZE} |" >> $GITHUB_STEP_SUMMARY + echo "| Container | \`${{env.CONTAINER_NAME}}:${{env.CONTAINER_TAG}}\` |" >> $GITHUB_STEP_SUMMARY + + - name: โซ Pushing the docker container... + env: + CONTAINER_NAME: ${{inputs.container_name || 'frontend/dev.arolariu'}} + CONTAINER_TAG: ${{inputs.environment == 'production' && github.sha || 'latest'}} + run: | + echo "::group::โซ Docker Push" + echo "๐Ÿ“ค Pushing to Azure Container Registry..." + START_TIME=$(date +%s) + docker push ${{ secrets.AZURE_CONTAINER_REGISTRY_ADDRESS }}/${{env.CONTAINER_NAME}}:${{env.CONTAINER_TAG}} + END_TIME=$(date +%s) + DURATION=$((END_TIME - START_TIME)) + echo "::notice::โœ… Docker push completed in ${DURATION}s" + echo "::endgroup::" + + echo "## โซ Docker Push Results" >> $GITHUB_STEP_SUMMARY + echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY + echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| Push Duration | ${DURATION}s |" >> $GITHUB_STEP_SUMMARY + echo "| Registry | Azure Container Registry |" >> $GITHUB_STEP_SUMMARY