Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

refactor(plugin-dashboard): drop the unreachable DatasetWidget arm from DashboardRenderer's self-contained branch #8364

refactor(plugin-dashboard): drop the unreachable DatasetWidget arm from DashboardRenderer's self-contained branch

refactor(plugin-dashboard): drop the unreachable DatasetWidget arm from DashboardRenderer's self-contained branch #8364

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();
}