Skip to content

chore: release packages #5966

chore: release packages

chore: release packages #5966

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: |
# Performance budget: main entry must be < 350 KB gzip
# This is a realistic threshold for a full-featured enterprise app
# with React, routing, UI components, and core business logic.
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"
# Check budget
OVER=$(awk "BEGIN {print ($GZIP_KB > $MAX_ENTRY_GZIP_KB) ? 1 : 0}")
if [ "$OVER" -eq 1 ]; then
echo ""
echo "❌ BUDGET EXCEEDED: Main entry is ${GZIP_KB} KB gzip (limit: ${MAX_ENTRY_GZIP_KB} KB)"
echo "budget_status=fail" >> "$GITHUB_OUTPUT"
exit 1
else
echo ""
echo "✅ Budget OK: Main entry is ${GZIP_KB} KB gzip (limit: ${MAX_ENTRY_GZIP_KB} KB)"
echo "budget_status=pass" >> "$GITHUB_OUTPUT"
fi
- 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_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();
}