From b2111adef5b9bd7f9a1f306883cc4dca6d5cc5d7 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Wed, 24 Jan 2024 21:02:12 +0530 Subject: [PATCH] Feat: Calculate package size change on PRs --- .github/workflows/e2e.yml | 45 +++++++++++++++++++++++++++++-- package.json | 1 + pnpm-lock.yaml | 3 +++ scripts/size.ts | 57 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 scripts/size.ts diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index b8232b8c0e..85a33e00c5 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -15,6 +15,7 @@ on: permissions: contents: read + pull-requests: write env: # For PRs and MergeQueues, the target commit is used, and for push events, github.event.previous is used. @@ -45,11 +46,26 @@ jobs: with: ref: ${{ env.targetHash }} + - name: Install dependencies + if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }} + uses: cypress-io/github-action@v6 + with: + # just perform install + runTests: false + + - name: Build + if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' && github.event_name == 'pull_request' }} + run: | + pnpm run build:viz + mkdir -p cypress/snapshots/stats/base + mv stats cypress/snapshots/stats/base + - name: Cypress run - uses: cypress-io/github-action@v4 + uses: cypress-io/github-action@v6 id: cypress-snapshot-gen if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }} with: + install: false start: pnpm run dev wait-on: 'http://localhost:9000' browser: chrome @@ -81,15 +97,27 @@ jobs: path: ./cypress/snapshots key: ${{ runner.os }}-snapshots-${{ env.targetHash }} + - name: Install dependencies + uses: cypress-io/github-action@v6 + with: + runTests: false + + - name: Build + if: ${{ github.event_name == 'pull_request' }} + run: | + pnpm run build:viz + mv stats cypress/snapshots/stats/head + # Install NPM dependencies, cache them correctly # and run all Cypress tests - name: Cypress run - uses: cypress-io/github-action@v4 + uses: cypress-io/github-action@v6 id: cypress # If CYPRESS_RECORD_KEY is set, run in parallel on all containers # Otherwise (e.g. if running from fork), we run on a single container only if: ${{ ( env.CYPRESS_RECORD_KEY != '' ) || ( matrix.containers == 1 ) }} with: + install: false start: pnpm run dev:coverage wait-on: 'http://localhost:9000' browser: chrome @@ -136,6 +164,19 @@ jobs: pattern: snapshots-* merge-multiple: true + - name: Calculate Size difference + id: size + if: ${{ github.event_name == 'pull_request' }} + run: echo "::set-output name=size_diff::$(npx tsx scripts/size.ts)" + + - name: Comment PR size difference + if: ${{ github.event_name == 'pull_request' }} + uses: thollander/actions-comment-pull-request@v2 + with: + message: | + ${{ steps.size.outputs.size_diff }} + comment_tag: size-diff + # For successful push events, we save the snapshots cache - name: Save snapshots cache id: cache-upload diff --git a/package.json b/package.json index ed0ae63a50..d923eb8595 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "jsdom": "^22.0.0", "langium-cli": "2.0.1", "lint-staged": "^13.2.1", + "markdown-table": "^3.0.3", "nyc": "^15.1.0", "path-browserify": "^1.0.1", "pnpm": "^8.6.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 31273ff6de..3a30a62a8c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -155,6 +155,9 @@ importers: lint-staged: specifier: ^13.2.1 version: 13.2.3 + markdown-table: + specifier: ^3.0.3 + version: 3.0.3 nyc: specifier: ^15.1.0 version: 15.1.0 diff --git a/scripts/size.ts b/scripts/size.ts new file mode 100644 index 0000000000..c6d3491e35 --- /dev/null +++ b/scripts/size.ts @@ -0,0 +1,57 @@ +/* eslint-disable no-console */ +import type { Metafile } from 'esbuild'; +import { readFile } from 'fs/promises'; +import { globby } from 'globby'; +import { markdownTable } from 'markdown-table'; +export const getSizes = (metafile: Metafile) => { + const { outputs } = metafile; + const sizes = Object.keys(outputs) + .filter((key) => key.endsWith('js') && !key.includes('chunk')) + .map((key) => { + const { bytes } = outputs[key]; + return [key.replace('dist/', ''), bytes]; + }); + return sizes; +}; + +const readStats = async (path: string): Promise> => { + const files = await globby(path); + const contents = await Promise.all(files.map((file) => readFile(file, 'utf-8'))); + const sizes = contents.flatMap((content) => getSizes(JSON.parse(content))); + return Object.fromEntries(sizes); +}; + +const percentageDifference = (oldValue: number, newValue: number): string => { + const difference = Math.abs(newValue - oldValue); + const avg = (newValue + oldValue) / 2; + const percentage = (difference / avg) * 100; + const roundedPercentage = percentage.toFixed(2); // Round to two decimal places + if (roundedPercentage === '0.00') { + return '0.00%'; + } + const sign = newValue > oldValue ? '+' : '-'; + return `${sign}${roundedPercentage}%`; +}; + +const main = async () => { + const oldStats = await readStats('./cypress/snapshots/base/*.json'); + const newStats = await readStats('./cypress/snapshots/head/*.json'); + const diff = Object.entries(newStats) + .map(([key, value]) => { + const oldValue = oldStats[key]; + const delta = value - oldValue; + return [key, oldValue, value, delta, percentageDifference(oldValue, value)].map((v) => + v.toString() + ); + }) + .filter(([, , , delta]) => delta !== '0'); + if (diff.length === 0) { + console.log('No changes in bundle sizes'); + return; + } + console.log( + markdownTable([['File', 'Previous Size', 'New Size', 'Difference', '% Change'], ...diff]) + ); +}; + +void main().catch((e) => console.error(e));