This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
refactor(plugin-dashboard): drop the unreachable DatasetWidget arm from DashboardRenderer's self-contained branch #8364
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: Bundle Analysis | |
| on: | |
| push: | |
| branches: [main, develop] | |
| paths: | |
| - 'packages/**' | |
| - 'apps/console/**' | |
| - 'pnpm-lock.yaml' | |
| pull_request: | |
| branches: [main, develop] | |
| paths: | |
| - 'packages/**' | |
| - 'apps/console/**' | |
| - 'pnpm-lock.yaml' | |
| concurrency: | |
| group: bundle-analysis-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| jobs: | |
| bundle-analysis: | |
| name: Bundle Analysis | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v7 | |
| with: | |
| submodules: true | |
| - name: Enable Corepack | |
| run: corepack enable | |
| - name: Verify pnpm version | |
| run: pnpm --version | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v7 | |
| with: | |
| node-version: '22.x' | |
| cache: 'pnpm' | |
| - name: Turbo Cache | |
| uses: actions/cache@v6 | |
| with: | |
| path: .turbo/cache | |
| key: turbo-${{ runner.os }}-${{ github.sha }} | |
| restore-keys: | | |
| turbo-${{ runner.os }}- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Build packages | |
| id: build_packages | |
| run: pnpm turbo run build --filter='./packages/*' | |
| - name: Build Console | |
| run: pnpm --filter @object-ui/console build | |
| - name: Check console performance budget | |
| id: budget | |
| run: | | |
| # TWO measurements, one verdict. | |
| # | |
| # 1. The `index-*.js` entry chunk against a 350 KB line. This is the | |
| # original budget and it stays: a fat entry chunk is still worth | |
| # its own signal, and replacing a gauge is not licence to weaken | |
| # the one already here. | |
| # 2. The EAGER CLOSURE — every chunk the entry reaches through STATIC | |
| # imports, i.e. everything the browser must fetch and parse before | |
| # the app renders. | |
| # | |
| # Measurement 1 alone was the whole budget until objectui#5324. On | |
| # `77f846a8b` the entry chunk gzips to 25.9 KB against the 350 KB line | |
| # while the closure it pulls in is 3,881,609 bytes across 58 of 507 | |
| # chunks — so the gate passed on 0.67% of the payload it claimed to | |
| # govern, and the 89 KiB regression of objectui#5266 landed in | |
| # `vendor-objectstack-*.js` where nothing here could see it. | |
| # `advancedChunks` routes vendor and workspace code into named chunks | |
| # on purpose, so MOST regressions land outside `index-*.js`. | |
| # | |
| # The closure ceiling is NOT set here. It lives in | |
| # `scripts/check-eager-closure-budget.mjs` next to the reasoning that | |
| # produced it, so a re-baseline is one edit against a documented | |
| # argument rather than a number in YAML: today's measured payload plus | |
| # ~2% of headroom, chosen to pass on today's `main` while staying | |
| # NARROWER than the 89 KiB regression the gate exists to catch. It is a | |
| # truthful current-state ceiling, not a statement that 3.79 MB is fine. | |
| # | |
| # Both measurements run before either may fail the step: a run that | |
| # reports one number and hides the other teaches readers to distrust | |
| # the comment, which is how objectui#3152 nearly took this gate down. | |
| MAX_ENTRY_GZIP_KB=350 | |
| DIST_DIR="apps/console/dist/assets" | |
| if [ ! -d "$DIST_DIR" ]; then | |
| echo "❌ Build output not found at $DIST_DIR" | |
| # Declare the outcome instead of leaving every output empty. An | |
| # empty `budget_status` used to render as a ❌ FAIL verdict | |
| # downstream (objectui#3152); the comment renderer now needs each | |
| # path to say what happened rather than infer it from silence. | |
| echo "budget_status=error" >> "$GITHUB_OUTPUT" | |
| echo "budget_message=Build output not found at $DIST_DIR" >> "$GITHUB_OUTPUT" | |
| exit 1 | |
| fi | |
| # Find the main entry chunk (index-*.js) | |
| ENTRY_FILE=$(find "$DIST_DIR" -name 'index-*.js' -not -name '*.gz' -not -name '*.br' | head -1) | |
| if [ -z "$ENTRY_FILE" ]; then | |
| echo "⚠️ Could not find main entry chunk, checking all JS files..." | |
| ENTRY_FILE=$(find "$DIST_DIR" -name '*.js' -not -name '*.gz' -not -name '*.br' | sort | head -1) | |
| fi | |
| if [ -z "$ENTRY_FILE" ]; then | |
| echo "❌ No JS files found in $DIST_DIR" | |
| echo "budget_status=error" >> "$GITHUB_OUTPUT" | |
| echo "budget_message=No JS files found in $DIST_DIR" >> "$GITHUB_OUTPUT" | |
| exit 1 | |
| fi | |
| echo "📦 Main entry file: $(basename $ENTRY_FILE)" | |
| # Calculate gzip size | |
| GZIP_BYTES=$(gzip -c "$ENTRY_FILE" | wc -c) | |
| GZIP_KB=$(awk "BEGIN {printf \"%.1f\", $GZIP_BYTES / 1024}") | |
| echo " Raw size: $(awk "BEGIN {printf \"%.1f\", $(wc -c < "$ENTRY_FILE") / 1024}") KB" | |
| echo " Gzip size: ${GZIP_KB} KB" | |
| echo " Budget: ${MAX_ENTRY_GZIP_KB} KB" | |
| echo "gzip_kb=$GZIP_KB" >> "$GITHUB_OUTPUT" | |
| echo "budget_kb=$MAX_ENTRY_GZIP_KB" >> "$GITHUB_OUTPUT" | |
| echo "entry_file=$(basename $ENTRY_FILE)" >> "$GITHUB_OUTPUT" | |
| ENTRY_OVER=$(awk "BEGIN {print ($GZIP_KB > $MAX_ENTRY_GZIP_KB) ? 1 : 0}") | |
| if [ "$ENTRY_OVER" -eq 1 ]; then | |
| echo "❌ ENTRY BUDGET EXCEEDED: Main entry is ${GZIP_KB} KB gzip (limit: ${MAX_ENTRY_GZIP_KB} KB)" | |
| else | |
| echo "✅ Entry budget OK: Main entry is ${GZIP_KB} KB gzip (limit: ${MAX_ENTRY_GZIP_KB} KB)" | |
| fi | |
| echo "" | |
| echo "📦 Eager closure (what a page load actually pays for):" | |
| # Writes closure_status / closure_gzip_kb / closure_budget_kb / | |
| # closure_chunks to $GITHUB_OUTPUT itself. Exit codes are distinct on | |
| # purpose: 1 = over budget (a verdict about the bundle), 2 = no | |
| # trustworthy measurement (a verdict about the gauge). Reporting one as | |
| # the other is how a broken gauge gets read as a size regression, and a | |
| # size regression as a broken gauge. | |
| set +e | |
| node scripts/check-eager-closure-budget.mjs | |
| CLOSURE_CODE=$? | |
| set -e | |
| if [ "$CLOSURE_CODE" -eq 2 ]; then | |
| echo "budget_status=error" >> "$GITHUB_OUTPUT" | |
| echo "budget_message=The entry chunk measured ${GZIP_KB} KB, but the eager-closure gauge produced no trustworthy measurement — see the step log. This is a broken gauge, not a passing budget." >> "$GITHUB_OUTPUT" | |
| exit 1 | |
| fi | |
| if [ "$ENTRY_OVER" -eq 1 ] || [ "$CLOSURE_CODE" -ne 0 ]; then | |
| echo "" | |
| echo "❌ BUDGET EXCEEDED" | |
| echo "budget_status=fail" >> "$GITHUB_OUTPUT" | |
| exit 1 | |
| fi | |
| echo "" | |
| echo "✅ Budget OK: entry chunk and eager closure are both within budget" | |
| echo "budget_status=pass" >> "$GITHUB_OUTPUT" | |
| - name: Generate package size report | |
| id: size-report | |
| # NOT `always()`. `always()` also fires on a cancelled run, where | |
| # `packages/*/dist` is only partly populated — the report was then | |
| # emitted from that partial tree and looked complete while silently | |
| # missing 7 packages (objectui#3152). The report is only meaningful | |
| # once every package has finished building, so gate it on that. | |
| if: ${{ !cancelled() && steps.build_packages.outcome == 'success' }} | |
| run: | | |
| echo "## 📦 Bundle Size Report" > size-report.md | |
| echo "" >> size-report.md | |
| echo "| Package | Size | Gzipped |" >> size-report.md | |
| echo "|---------|------|---------|" >> size-report.md | |
| for pkg in packages/*/dist; do | |
| if [ -d "$pkg" ]; then | |
| pkg_name=$(basename $(dirname $pkg)) | |
| # Calculate sizes for main bundle files | |
| for file in "$pkg"/*.js; do | |
| if [ -f "$file" ] && [ ! -f "${file}.map" ]; then | |
| # Use portable method to get file size | |
| size=$(wc -c < "$file") | |
| size_kb=$(awk "BEGIN {printf \"%.2f\", $size/1024}") | |
| # Estimate gzipped size | |
| gzip_size=$(gzip -c "$file" | wc -c) | |
| gzip_kb=$(awk "BEGIN {printf \"%.2f\", $gzip_size/1024}") | |
| echo "| $pkg_name ($(basename $file)) | ${size_kb}KB | ${gzip_kb}KB |" >> size-report.md | |
| fi | |
| done | |
| fi | |
| done | |
| echo "" >> size-report.md | |
| echo "### Size Limits" >> size-report.md | |
| echo "- ✅ Core packages should be < 50KB gzipped" >> size-report.md | |
| echo "- ✅ Component packages should be < 100KB gzipped" >> size-report.md | |
| echo "- ⚠️ Plugin packages should be < 150KB gzipped" >> size-report.md | |
| # Rendering lives in `scripts/render-budget-comment.mjs` so it can be unit | |
| # tested (`scripts/__tests__/render-budget-comment.test.ts`) — the bug this | |
| # replaces was purely a rendering bug, and logic inlined in YAML is | |
| # untestable. Step outputs are passed through `env` rather than | |
| # interpolated into a JS string literal, so an absent output stays an | |
| # empty *variable* instead of vanishing into source text. | |
| - name: Render performance budget comment | |
| id: render_comment | |
| # `!cancelled()`, NOT `always()`: a cancelled run measured nothing, and | |
| # the superseding run posts the real verdict moments later. Commenting | |
| # on cancellation is what produced a ❌ FAIL on every PR that got a | |
| # second push (objectui#3152). | |
| if: ${{ github.event_name == 'pull_request' && !cancelled() }} | |
| env: | |
| BUDGET_STATUS: ${{ steps.budget.outputs.budget_status }} | |
| BUDGET_MESSAGE: ${{ steps.budget.outputs.budget_message }} | |
| BUDGET_GZIP_KB: ${{ steps.budget.outputs.gzip_kb }} | |
| BUDGET_LIMIT_KB: ${{ steps.budget.outputs.budget_kb }} | |
| BUDGET_ENTRY_FILE: ${{ steps.budget.outputs.entry_file }} | |
| BUDGET_CLOSURE_STATUS: ${{ steps.budget.outputs.closure_status }} | |
| BUDGET_CLOSURE_GZIP_KB: ${{ steps.budget.outputs.closure_gzip_kb }} | |
| BUDGET_CLOSURE_BUDGET_KB: ${{ steps.budget.outputs.closure_budget_kb }} | |
| BUDGET_CLOSURE_CHUNKS: ${{ steps.budget.outputs.closure_chunks }} | |
| BUDGET_STEP_OUTCOME: ${{ steps.budget.outcome }} | |
| BUILD_PACKAGES_OUTCOME: ${{ steps.build_packages.outcome }} | |
| run: node scripts/render-budget-comment.mjs > budget-comment.md | |
| - name: Comment PR with results | |
| if: ${{ github.event_name == 'pull_request' && !cancelled() }} | |
| continue-on-error: true | |
| uses: actions/github-script@v9 | |
| with: | |
| # GitHub's API intermittently rejects valid run tokens with 401 | |
| # (seen on PR #1627), so 401/403 must stay retryable here. | |
| retries: 3 | |
| retry-exempt-status-codes: 400,404,422 | |
| script: | | |
| const fs = require('fs'); | |
| if (!fs.existsSync('budget-comment.md')) { | |
| core.warning('budget-comment.md was not rendered; skipping PR comment.'); | |
| return; | |
| } | |
| const body = fs.readFileSync('budget-comment.md', 'utf8'); | |
| try { | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body | |
| }); | |
| } catch (error) { | |
| core.warning(`Could not comment on PR: ${error.message}`); | |
| await core.summary.addRaw(body).write(); | |
| } |