feat(debugger): Add event tracing and runtime diagnostics #401
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: PR Validation | |
| on: | |
| pull_request: | |
| branches: [ "main" ] | |
| types: [opened, synchronize, reopened, ready_for_review] | |
| # Ensure only one workflow runs per PR | |
| concurrency: | |
| group: pr-${{ github.event.pull_request.number }} | |
| cancel-in-progress: true | |
| jobs: | |
| # Validate PR title follows Conventional Commits | |
| validate-pr-title: | |
| name: Validate PR Title | |
| runs-on: ubuntu-latest | |
| if: github.event.pull_request.draft == false && github.event.pull_request.user.login != 'dependabot[bot]' | |
| steps: | |
| - name: Validate PR title | |
| uses: amannn/action-semantic-pull-request@v6 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| types: | | |
| feat | |
| fix | |
| docs | |
| style | |
| refactor | |
| perf | |
| test | |
| build | |
| ci | |
| chore | |
| revert | |
| requireScope: false | |
| subjectPattern: ^[A-Z].+$ | |
| subjectPatternError: | | |
| The subject "{subject}" found in the pull request title "{title}" | |
| didn't match the configured pattern. Please ensure that the subject | |
| starts with an uppercase character. | |
| # Validate PR has description | |
| validate-pr-description: | |
| name: Validate PR Description | |
| runs-on: ubuntu-latest | |
| if: github.event.pull_request.draft == false && github.event.pull_request.user.login != 'dependabot[bot]' | |
| steps: | |
| - name: Check PR description | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const prBody = context.payload.pull_request.body || ''; | |
| const minLength = 50; | |
| if (prBody.trim().length < minLength) { | |
| core.setFailed( | |
| `PR description is too short (${prBody.trim().length} chars). ` + | |
| `Please provide a meaningful description (minimum ${minLength} chars).` | |
| ); | |
| return; | |
| } | |
| // Check for required sections (flexible check) | |
| const hasWhat = /###?\s*What/i.test(prBody); | |
| const hasWhy = /###?\s*Why/i.test(prBody); | |
| const hasTesting = /###?\s*Testing/i.test(prBody) || /\[x\].*test/i.test(prBody); | |
| if (!hasWhat || !hasWhy) { | |
| core.setFailed( | |
| 'PR description is missing required sections. ' + | |
| 'Please use the PR template and fill in: What, Why, and Testing sections.' | |
| ); | |
| return; | |
| } | |
| core.info('✅ PR description looks good!'); | |
| # Check PR size | |
| check-pr-size: | |
| name: Check PR Size | |
| runs-on: ubuntu-latest | |
| if: github.event.pull_request.draft == false | |
| steps: | |
| - name: Check PR size | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const pr = context.payload.pull_request; | |
| const additions = pr.additions || 0; | |
| const deletions = pr.deletions || 0; | |
| const totalChanges = additions + deletions; | |
| const files = await github.paginate(github.rest.pulls.listFiles, { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| pull_number: pr.number, | |
| per_page: 100, | |
| }); | |
| const isMaintenancePath = (path) => { | |
| return ( | |
| path.startsWith('.squad/') || | |
| path.startsWith('docs/') || | |
| path.startsWith('.github/instructions/') || | |
| /(^|\/)README(\..+)?$/i.test(path) || | |
| /(^|\/)CHANGELOG(\..+)?$/i.test(path) || | |
| /(^|\/)CONTRIBUTING(\..+)?$/i.test(path) || | |
| /(^|\/)SECURITY(\..+)?$/i.test(path) || | |
| /(^|\/)LICENSE(\..+)?$/i.test(path) || | |
| /(^|\/)BENCHMARK_PR_STATUS(\..+)?$/i.test(path) || | |
| path.endsWith('.md') | |
| ); | |
| }; | |
| const maintenanceOnly = files.length > 0 && files.every((f) => isMaintenancePath(f.filename)); | |
| // Soft limit (warning) and hard limit (failure) | |
| const hardLimit = 1500; | |
| const softLimit = 400; | |
| if (totalChanges > hardLimit) { | |
| if (maintenanceOnly) { | |
| core.warning( | |
| `⚠️ Large maintenance-only PR (${totalChanges} lines changed). ` + | |
| `Allowing because all changed files are docs/.squad/instructions metadata.` | |
| ); | |
| return; | |
| } | |
| core.setFailed( | |
| `⚠️ PR is too large (${totalChanges} lines changed). ` + | |
| `Please consider breaking it into smaller PRs (< ${hardLimit} lines).` | |
| ); | |
| return; | |
| } | |
| if (totalChanges > softLimit) { | |
| core.warning( | |
| `⚠️ PR is getting large (${totalChanges} lines changed). ` + | |
| `Consider breaking it into smaller PRs for easier review.` | |
| ); | |
| } else { | |
| core.info(`✅ PR size is good (${totalChanges} lines changed)`); | |
| } | |
| # Lint PR changes | |
| lint-check: | |
| name: Lint Check | |
| runs-on: ubuntu-latest | |
| if: github.event.pull_request.draft == false | |
| # Pin the SDK to the version in global.json so that `dotnet format` | |
| # produces identical output regardless of which SDK the runner ships. | |
| # Without this, a newer SDK (e.g. 10.0.201 vs 10.0.103) may apply | |
| # different formatting rules, causing false-positive lint failures. | |
| env: | |
| DOTNET_ROLL_FORWARD: LatestPatch | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup .NET (from global.json) | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| global-json-file: global.json | |
| - name: Install WASM workloads | |
| run: dotnet workload install wasm-experimental wasm-tools | |
| - name: Restore | |
| run: dotnet restore | |
| env: | |
| # Suppress NuGet audit during lint restore — audit is not the lint | |
| # check's concern; the security-scan job handles vulnerability detection. | |
| DOTNET_NUGET_AUDIT: "false" | |
| - name: Get changed C# files | |
| id: changed-files | |
| run: | | |
| # Get list of changed .cs files in the PR | |
| CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT origin/${{ github.event.pull_request.base.ref }}...HEAD | grep '\.cs$' || true) | |
| if [ -z "$CHANGED_FILES" ]; then | |
| echo "has_cs_files=false" >> $GITHUB_OUTPUT | |
| echo "ℹ️ No C# files changed in this PR" | |
| else | |
| echo "has_cs_files=true" >> $GITHUB_OUTPUT | |
| # Convert newlines to spaces and store | |
| FILES_SPACE_SEPARATED=$(echo "$CHANGED_FILES" | tr '\n' ' ') | |
| echo "files=$FILES_SPACE_SEPARATED" >> $GITHUB_OUTPUT | |
| echo "📝 Changed C# files:" | |
| echo "$CHANGED_FILES" | |
| fi | |
| - name: Format check changed files | |
| if: steps.changed-files.outputs.has_cs_files == 'true' | |
| run: | | |
| # Check formatting only for changed files | |
| FILES="${{ steps.changed-files.outputs.files }}" | |
| echo "🔍 Checking formatting for changed files..." | |
| echo "Files: $FILES" | |
| # Run format check with --include for each file. | |
| # --exclude-diagnostics IDE1006: Exclude naming-rule violations | |
| # (e.g. "Missing prefix: '_'") because dotnet format can detect but | |
| # NOT auto-fix them (renaming is a refactoring, not formatting). | |
| # Without this flag, --verify-no-changes reports false positives for | |
| # files that would be "Formatted" yet produce zero diff. | |
| dotnet format --verify-no-changes --include $FILES --verbosity diagnostic \ | |
| --exclude-diagnostics IDE1006 | |
| if [ $? -ne 0 ]; then | |
| echo "" | |
| echo "❌ Code formatting issues detected in your changes." | |
| echo "Please run the following command locally:" | |
| echo " dotnet format --include $FILES" | |
| echo "" | |
| exit 1 | |
| fi | |
| echo "✅ Code formatting is correct for all changed files" | |
| - name: Skip format check | |
| if: steps.changed-files.outputs.has_cs_files == 'false' | |
| run: | | |
| echo "✅ No C# files to check - skipping format validation" | |
| # Security scan | |
| security-scan: | |
| name: Security Scan | |
| runs-on: ubuntu-latest | |
| if: github.event.pull_request.draft == false | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Install gitleaks CLI | |
| run: | | |
| GITLEAKS_VERSION="8.28.0" | |
| curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" -o /tmp/gitleaks.tar.gz | |
| tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks | |
| /tmp/gitleaks version | |
| - name: Secrets scan (gitleaks) | |
| run: /tmp/gitleaks git --redact --config .gitleaks.toml | |
| - name: Setup .NET 10 | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '10.0.x' | |
| - name: Install WASM workloads | |
| run: dotnet workload install wasm-experimental wasm-tools | |
| - name: Restore | |
| run: dotnet restore | |
| env: | |
| # Suppress audit during restore — we run our own scan below | |
| DOTNET_NUGET_AUDIT: "false" | |
| - name: Check for vulnerable direct packages | |
| run: | | |
| echo "🔍 Scanning direct packages for vulnerabilities..." | |
| dotnet list package --vulnerable 2>&1 | tee direct-vulnerability-report.txt | |
| if grep -qi "critical\|high" direct-vulnerability-report.txt; then | |
| echo "❌ Critical or High severity vulnerabilities detected in direct dependencies!" | |
| echo "Please update the affected packages before merging." | |
| exit 1 | |
| else | |
| echo "✅ No critical or high severity vulnerabilities in direct dependencies" | |
| fi | |
| - name: Check for vulnerable transitive packages | |
| run: | | |
| echo "🔍 Scanning transitive packages for vulnerabilities..." | |
| dotnet list package --vulnerable --include-transitive 2>&1 | tee transitive-vulnerability-report.txt | |
| if grep -qi "critical\|high" transitive-vulnerability-report.txt; then | |
| echo "❌ Critical or High severity vulnerabilities detected in transitive dependencies!" | |
| echo "These usually require updating one or more direct package versions." | |
| echo "Review the report above for details." | |
| # Allow temporary exceptions via ALLOW_TRANSITIVE_VULNS env var. | |
| # Set it to a tracking issue URL to document the exception. | |
| if [ -n "${ALLOW_TRANSITIVE_VULNS:-}" ]; then | |
| if echo "${ALLOW_TRANSITIVE_VULNS}" | grep -Eq '^https?://'; then | |
| echo "⚠️ Temporarily allowing transitive vulnerabilities due to tracked issue:" | |
| echo " ${ALLOW_TRANSITIVE_VULNS}" | |
| echo "Ensure this exception is revisited and removed before the next release." | |
| else | |
| echo "❌ ALLOW_TRANSITIVE_VULNS is set but is not a valid http(s) URL:" | |
| echo " ${ALLOW_TRANSITIVE_VULNS}" | |
| echo "Please set it to a link to the tracking issue or security exception document." | |
| exit 1 | |
| fi | |
| else | |
| echo "❌ Failing build because high/critical transitive vulnerabilities were found." | |
| echo "To temporarily allow this (with explicit tracking), set ALLOW_TRANSITIVE_VULNS" | |
| echo "to the URL of the issue or security exception that documents the risk." | |
| exit 1 | |
| fi | |
| else | |
| echo "✅ No critical or high severity vulnerabilities in transitive dependencies" | |
| fi | |
| # Template smoke test — scaffold and build each dotnet new template | |
| template-smoke-test: | |
| name: Template Smoke Test | |
| runs-on: ubuntu-latest | |
| if: github.event.pull_request.draft == false | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| # Nerdbank.GitVersioning requires full git history to compute versions | |
| fetch-depth: 0 | |
| - name: Setup .NET 10 | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '10.0.x' | |
| - name: Install WASM workloads | |
| run: dotnet workload install wasm-experimental wasm-tools | |
| - name: Pack framework packages to local feed | |
| run: | | |
| LOCAL_FEED=$(pwd)/local-packages | |
| mkdir -p "$LOCAL_FEED" | |
| # Use a version that matches the template's 1.0.0-* floating constraint | |
| PACK_VERSION="1.0.0-ci" | |
| # Pack framework projects in dependency order. | |
| # -p:Version overrides Nerdbank.GitVersioning so ProjectReference | |
| # dependencies also resolve as 1.0.0-ci in the local feed. | |
| for project in \ | |
| Picea.Abies/Picea.Abies.csproj \ | |
| Picea.Abies.Browser/Picea.Abies.Browser.csproj \ | |
| Picea.Abies.Server/Picea.Abies.Server.csproj \ | |
| Picea.Abies.Server.Kestrel/Picea.Abies.Server.Kestrel.csproj; do | |
| echo "📦 Packing $project as $PACK_VERSION..." | |
| dotnet pack "$project" -c Release -o "$LOCAL_FEED" \ | |
| -p:Version="$PACK_VERSION" \ | |
| -p:PackageVersion="$PACK_VERSION" | |
| done | |
| echo "" | |
| echo "📦 Local packages:" | |
| ls -la "$LOCAL_FEED" | |
| env: | |
| DOTNET_NUGET_AUDIT: "false" | |
| - name: Create NuGet config for template builds | |
| run: | | |
| # Create a nuget.config that prioritises the local feed for framework | |
| # packages but falls back to nuget.org for transitive dependencies | |
| # (e.g. Picea, Praefixum, Microsoft.AspNetCore.App). | |
| cat > /tmp/template-nuget.config <<EOF | |
| <?xml version="1.0" encoding="utf-8"?> | |
| <configuration> | |
| <packageSources> | |
| <clear /> | |
| <add key="local" value="$(pwd)/local-packages" /> | |
| <add key="nuget.org" value="https://api.nuget.org/v3/index.json" /> | |
| </packageSources> | |
| </configuration> | |
| EOF | |
| echo "📄 NuGet config:" | |
| cat /tmp/template-nuget.config | |
| - name: Install template pack from source | |
| run: | | |
| dotnet new install Picea.Abies.Templates/ | |
| echo "" | |
| echo "📋 Installed templates:" | |
| dotnet new list abies | |
| - name: Detect template-related changes | |
| id: template-changes | |
| run: | | |
| CHANGED=$(git diff --name-only --diff-filter=ACMRT origin/${{ github.event.pull_request.base.ref }}...HEAD | grep '^Picea\.Abies\.Templates/' || true) | |
| if [ -n "$CHANGED" ]; then | |
| echo "has_template_changes=true" >> $GITHUB_OUTPUT | |
| echo "Detected template changes:" | |
| echo "$CHANGED" | |
| else | |
| echo "has_template_changes=false" >> $GITHUB_OUTPUT | |
| echo "No template source changes detected" | |
| fi | |
| - name: Smoke test — abies-browser | |
| run: | | |
| TEMP_DIR=$(mktemp -d) | |
| echo "🧪 Scaffolding abies-browser into $TEMP_DIR..." | |
| dotnet new abies-browser -o "$TEMP_DIR" | |
| echo "🔨 Restoring scaffolded project..." | |
| dotnet restore "$TEMP_DIR" --configfile /tmp/template-nuget.config | |
| echo "🔨 Building scaffolded project..." | |
| dotnet build "$TEMP_DIR" --no-restore | |
| echo "✅ abies-browser template compiles successfully" | |
| rm -rf "$TEMP_DIR" | |
| - name: Smoke test — abies-browser-empty | |
| run: | | |
| TEMP_DIR=$(mktemp -d) | |
| echo "🧪 Scaffolding abies-browser-empty into $TEMP_DIR..." | |
| dotnet new abies-browser-empty -o "$TEMP_DIR" | |
| echo "🔨 Restoring scaffolded project..." | |
| dotnet restore "$TEMP_DIR" --configfile /tmp/template-nuget.config | |
| echo "🔨 Building scaffolded project..." | |
| dotnet build "$TEMP_DIR" --no-restore | |
| echo "✅ abies-browser-empty template compiles successfully" | |
| rm -rf "$TEMP_DIR" | |
| - name: Smoke test — abies-server | |
| run: | | |
| TEMP_DIR=$(mktemp -d) | |
| echo "🧪 Scaffolding abies-server into $TEMP_DIR..." | |
| dotnet new abies-server -o "$TEMP_DIR" | |
| echo "🔨 Restoring scaffolded project..." | |
| dotnet restore "$TEMP_DIR" --configfile /tmp/template-nuget.config | |
| echo "🔨 Building scaffolded project..." | |
| dotnet build "$TEMP_DIR" --no-restore | |
| echo "✅ abies-server template compiles successfully" | |
| rm -rf "$TEMP_DIR" | |
| - name: Security scan scaffolded templates | |
| if: steps.template-changes.outputs.has_template_changes == 'true' | |
| run: | | |
| OUT_ROOT=$(mktemp -d) | |
| echo "Using temp output root: $OUT_ROOT" | |
| dotnet new abies-browser -o "$OUT_ROOT/abies-browser" | |
| dotnet new abies-browser-empty -o "$OUT_ROOT/abies-browser-empty" | |
| dotnet new abies-server -o "$OUT_ROOT/abies-server" | |
| for project in \ | |
| "$OUT_ROOT/abies-browser" \ | |
| "$OUT_ROOT/abies-browser-empty" \ | |
| "$OUT_ROOT/abies-server"; do | |
| dotnet restore "$project" --configfile /tmp/template-nuget.config | |
| dotnet list "$project" package --vulnerable --include-transitive 2>&1 | tee "$project/vulnerability-report.txt" | |
| if grep -qi "critical\|high" "$project/vulnerability-report.txt"; then | |
| echo "❌ High/Critical dependency vulnerabilities in scaffolded project: $project" | |
| exit 1 | |
| fi | |
| done | |
| TRIVY_IMAGE="" | |
| for candidate in ghcr.io/aquasecurity/trivy:latest aquasec/trivy:latest; do | |
| if docker pull "$candidate" >/dev/null 2>&1; then | |
| TRIVY_IMAGE="$candidate" | |
| break | |
| fi | |
| done | |
| if [ -z "$TRIVY_IMAGE" ]; then | |
| echo "❌ Could not pull a usable Trivy image." | |
| exit 1 | |
| fi | |
| docker run --rm \ | |
| -v "$PWD:/src" \ | |
| -v "$OUT_ROOT:/scan" \ | |
| "$TRIVY_IMAGE" fs \ | |
| --severity HIGH,CRITICAL \ | |
| --ignore-unfixed \ | |
| --scanners vuln,misconfig,secret \ | |
| --exit-code 1 \ | |
| /scan | |
| python3 -m pip install --user semgrep | |
| export PATH="$HOME/.local/bin:$PATH" | |
| semgrep scan --config .semgrep/rules/template-security.yml "$OUT_ROOT" --error | |
| # Bundle size quality gate | |
| bundle-size-check: | |
| name: Bundle Size Check | |
| runs-on: ubuntu-latest | |
| if: github.event.pull_request.draft == false | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| # Nerdbank.GitVersioning requires full git history to compute versions | |
| fetch-depth: 0 | |
| - name: Setup .NET 10 | |
| uses: actions/setup-dotnet@v4 | |
| with: | |
| dotnet-version: '10.0.x' | |
| - name: Install WASM workloads | |
| run: dotnet workload install wasm-experimental wasm-tools | |
| - name: Publish Release (Trimmed) | |
| run: | | |
| dotnet publish Picea.Abies.Conduit.Wasm/Picea.Abies.Conduit.Wasm.csproj \ | |
| -c Release | |
| env: | |
| # Suppress NuGet audit — the security-scan job handles vulnerability detection | |
| DOTNET_NUGET_AUDIT: "false" | |
| - name: Measure Bundle Size | |
| id: bundle-size | |
| run: | | |
| # For browser-wasm projects using Microsoft.NET.Sdk, the publish output | |
| # goes to bin/Release/<tfm>/browser-wasm/AppBundle/ (not wwwroot/_framework). | |
| # The _framework directory contains the WASM runtime and assemblies. | |
| PUBLISH_DIR="Picea.Abies.Conduit.Wasm/bin/Release/net10.0/browser-wasm" | |
| # Find the _framework directory in the publish output | |
| FRAMEWORK_DIR="" | |
| for candidate in \ | |
| "$PUBLISH_DIR/AppBundle/_framework" \ | |
| "$PUBLISH_DIR/publish/wwwroot/_framework" \ | |
| "$PUBLISH_DIR/publish/_framework"; do | |
| if [ -d "$candidate" ]; then | |
| FRAMEWORK_DIR="$candidate" | |
| break | |
| fi | |
| done | |
| if [ -z "$FRAMEWORK_DIR" ]; then | |
| echo "❌ Could not find _framework directory in publish output" | |
| echo "Searched in: $PUBLISH_DIR" | |
| echo "Directory contents:" | |
| find "$PUBLISH_DIR" -maxdepth 4 -type d 2>/dev/null || echo " (directory not found)" | |
| exit 1 | |
| fi | |
| echo "📂 Found framework directory: $FRAMEWORK_DIR" | |
| # Get total size in bytes | |
| TOTAL_BYTES=$(du -sb "$FRAMEWORK_DIR" | cut -f1) | |
| TOTAL_MB=$((TOTAL_BYTES / 1024 / 1024)) | |
| FILE_COUNT=$(find "$FRAMEWORK_DIR" -type f | wc -l) | |
| echo "bundle_size_mb=$TOTAL_MB" >> $GITHUB_OUTPUT | |
| echo "bundle_size_bytes=$TOTAL_BYTES" >> $GITHUB_OUTPUT | |
| echo "file_count=$FILE_COUNT" >> $GITHUB_OUTPUT | |
| echo "📦 WASM Bundle Size Report" | |
| echo "==========================" | |
| echo "Total Size: ${TOTAL_MB}MB ($TOTAL_BYTES bytes)" | |
| echo "File Count: $FILE_COUNT" | |
| echo "" | |
| echo "Largest files:" | |
| find "$FRAMEWORK_DIR" -type f -exec du -h {} + | sort -rh | head -10 | |
| - name: Check Bundle Size Limits | |
| run: | | |
| BUNDLE_SIZE=${{ steps.bundle-size.outputs.bundle_size_mb }} | |
| # Validate BUNDLE_SIZE is a valid integer | |
| if ! echo "$BUNDLE_SIZE" | grep -qE '^[0-9]+$'; then | |
| echo "❌ FAILED: Could not determine bundle size (got '$BUNDLE_SIZE')" | |
| exit 1 | |
| fi | |
| # Hard limit: 15MB for trimmed Release build | |
| HARD_LIMIT=15 | |
| # Soft limit (warning): 10MB | |
| SOFT_LIMIT=10 | |
| echo "📊 Bundle Size: ${BUNDLE_SIZE}MB" | |
| echo "🔴 Hard Limit: ${HARD_LIMIT}MB" | |
| echo "🟡 Soft Limit: ${SOFT_LIMIT}MB" | |
| if [ "$BUNDLE_SIZE" -gt "$HARD_LIMIT" ]; then | |
| echo "" | |
| echo "❌ FAILED: Bundle size (${BUNDLE_SIZE}MB) exceeds hard limit (${HARD_LIMIT}MB)" | |
| echo "" | |
| echo "The WASM bundle is too large. Please:" | |
| echo "1. Ensure PublishTrimmed=true is set for Release configuration" | |
| echo "2. Review and remove unnecessary dependencies" | |
| echo "3. Enable InvariantGlobalization if not already done" | |
| echo "4. Consider code splitting if applicable" | |
| exit 1 | |
| elif [ "$BUNDLE_SIZE" -gt "$SOFT_LIMIT" ]; then | |
| echo "" | |
| echo "⚠️ WARNING: Bundle size (${BUNDLE_SIZE}MB) exceeds soft limit (${SOFT_LIMIT}MB)" | |
| echo "Consider optimizing bundle size for faster startup times." | |
| else | |
| echo "" | |
| echo "✅ Bundle size is within acceptable limits" | |
| fi | |
| # Check for TODO/FIXME without issues | |
| check-todos: | |
| name: Check TODOs | |
| runs-on: ubuntu-latest | |
| if: github.event.pull_request.draft == false | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Check for untracked TODOs | |
| run: | | |
| # Find TODO/FIXME comments without issue references | |
| untracked=$(grep -rIn "TODO\|FIXME" --include="*.cs" --include="*.fs" --exclude-dir=obj --exclude-dir=bin --exclude-dir=.git --binary-files=without-match . | grep -v "#[0-9]" || true) | |
| if [ ! -z "$untracked" ]; then | |
| echo "⚠️ Found TODO/FIXME comments without issue references:" | |
| echo "$untracked" | |
| echo "" | |
| echo "Please either:" | |
| echo "1. Create an issue and reference it (e.g., // TODO #123: description)" | |
| echo "2. Fix the item in this PR" | |
| echo "3. Remove the comment if not needed" | |
| exit 1 | |
| else | |
| echo "✅ No untracked TODOs found" | |
| fi | |
| # Summary | |
| pr-validation-summary: | |
| name: PR Validation Summary | |
| runs-on: ubuntu-latest | |
| if: github.event.pull_request.draft == false | |
| needs: [validate-pr-title, validate-pr-description, check-pr-size, lint-check, security-scan, template-smoke-test, bundle-size-check, check-todos] | |
| steps: | |
| - name: Check if automated PR | |
| id: check-automated | |
| run: | | |
| if [ "${{ github.event.pull_request.user.login }}" = "dependabot[bot]" ]; then | |
| echo "automated=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "automated=false" >> $GITHUB_OUTPUT | |
| fi | |
| - name: All checks passed | |
| run: | | |
| if [ "${{ steps.check-automated.outputs.automated }}" = "true" ]; then | |
| echo "🤖 Automated PR validation summary" | |
| echo "✅ PR size is reasonable" | |
| echo "✅ Code formatting is correct" | |
| echo "✅ No security vulnerabilities" | |
| echo "✅ Templates scaffold and compile" | |
| echo "✅ Bundle size within limits" | |
| echo "✅ No untracked TODOs" | |
| echo "" | |
| echo "Note: Title and description checks skipped for automated PRs" | |
| else | |
| echo "🎉 All PR validation checks passed!" | |
| echo "✅ PR title follows Conventional Commits" | |
| echo "✅ PR has adequate description" | |
| echo "✅ PR size is reasonable" | |
| echo "✅ Code formatting is correct" | |
| echo "✅ No security vulnerabilities" | |
| echo "✅ Templates scaffold and compile" | |
| echo "✅ Bundle size within limits" | |
| echo "✅ No untracked TODOs" | |
| echo "" | |
| echo "Next steps:" | |
| echo "1. Wait for CD and E2E workflows to complete" | |
| echo "2. Request review from team members" | |
| echo "3. Address any feedback" | |
| echo "4. Merge when approved!" | |
| fi |