From c6d4db634468f97a02694ce4880efcc2a9b8beaa Mon Sep 17 00:00:00 2001 From: Hugo Alliaume Date: Sun, 13 Oct 2024 08:42:46 +0200 Subject: [PATCH] Add CI workflow to compute diff between files dist files --- .github/generate-dist-files-size-diff.mjs | 177 ++++++++++++++++++ .../dist-files-size-diff-comment.yaml | 22 +++ .github/workflows/dist-files-size-diff.yaml | 68 +++++++ 3 files changed, 267 insertions(+) create mode 100644 .github/generate-dist-files-size-diff.mjs create mode 100644 .github/workflows/dist-files-size-diff-comment.yaml create mode 100644 .github/workflows/dist-files-size-diff.yaml diff --git a/.github/generate-dist-files-size-diff.mjs b/.github/generate-dist-files-size-diff.mjs new file mode 100644 index 00000000000..fc909daf1a1 --- /dev/null +++ b/.github/generate-dist-files-size-diff.mjs @@ -0,0 +1,177 @@ +/** + * Generate a markdown table with the difference in size of the dist files between the base and the PR. + */ + +/* +Usage: +```shell +BASE_DIST_FILES='{"src/Autocomplete/assets/dist/controller.js":{"size":15382,"size_gz":3716},"src/Chartjs/assets/dist/controller.js":{"size":2281,"size_gz":771},"src/Cropperjs/assets/dist/controller.js":{"size":1044,"size_gz":475}}' \ +PR_DIST_FILES='{"src/Chartjs/assets/dist/controller.js":{"size":1281,"size_gz":171},"src/Cropperjs/assets/dist/controller.js":{"size":1044,"size_gz":475},"src/Cropperjs/assets/dist/style.min.css":{"size":32,"size_gz":66},"src/Dropzone/assets/dist/controller.js":{"size":3199,"size_gz":816},"src/Map/src/Bridge/Google/assets/dist/foo.js":{"size":3199,"size_gz":816}}' \ +GITHUB_REPOSITORY='symfony/ux' \ +GITHUB_HEAD_REF='my-branch-name' \ + node .github/generate-dist-files-size-diff.mjs +``` + */ + +if (!process.env.BASE_DIST_FILES) { + throw new Error('Missing or invalid "BASE_DIST_FILES" env variable.'); +} + +if (!process.env.PR_DIST_FILES) { + throw new Error('Missing or invalid "PR_DIST_FILES" env variable.'); +} + +if (!process.env.GITHUB_REPOSITORY) { + throw new Error('Missing or invalid "GITHUB_REPOSITORY" env variable.'); +} + +if (!process.env.GITHUB_HEAD_REF) { + throw new Error('Missing or invalid "GITHUB_HEAD_REF" env variable.'); +} + +/** + * Adapted from https://gist.github.com/zentala/1e6f72438796d74531803cc3833c039c?permalink_comment_id=4455218#gistcomment-4455218 + * @param {number} bytes + * @param {number} digits + * @returns {string} + */ +function formatBytes(bytes, digits = 2) { + if (bytes === 0) { + return '0 B'; + } + const sizes = [`B`, 'kB', 'MB']; + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + + return parseFloat((bytes / Math.pow(1024, i)).toFixed(digits)) + ' ' + sizes[i]; +} + +/** + * @param {number} from + * @param {number} to + * @returns {number} + */ +function computeDiffPercent(from, to) { + if (from === to) { + return 0; + } + + return Math.round((from - to) / from * -100); +} + +/** + * @param {number} percent + * @returns {string} + */ +function formatDiffPercent(percent) { + return percent > 0 ? `+${percent}% 📈` : percent < 0 ? `${percent}% 📉` : `${percent}%`; +} + +export function main() { + const repoUrl = `https://github.com/${process.env.GITHUB_REPOSITORY}`; + /** @type {Record} */ + const base = JSON.parse(process.env.BASE_DIST_FILES); + /** @type {Record} */ + const pr = JSON.parse(process.env.PR_DIST_FILES); + let output = '

📊 Dist packagesFiles size difference

\n\n'; + + /** + * @type {Map + * }>} + */ + const packagesFiles = [...new Set([...Object.keys(pr), ...Object.keys(base)])] + .sort() + .reduce((acc, file) => { + const beforeSize = base[file]?.size || 0; + const afterSize = pr[file]?.size || 0; + const beforeSizeGz = base[file]?.size_gz || 0; + const afterSizeGz = pr[file]?.size_gz || 0; + + if (beforeSize !== afterSize) { + const isBridge = file.includes('src/Bridge'); // we assume that's enough for now + const packageName = file.split('/')[1]; + const bridgeName = isBridge ? file.split('/')[4] : ''; + const key = isBridge ? `${packageName} (Bridge ${bridgeName})` : packageName; + if (!acc.has(key)) { + acc.set(key, { + meta: { + packageName, + bridgeName, + url: isBridge ? `${repoUrl}/tree/${process.env.GITHUB_HEAD_REF}/src/${packageName}/src/Bridge/${bridgeName}/assets/dist` : `${repoUrl}/tree/${process.env.GITHUB_HEAD_REF}/src/${packageName}/assets/dist`, + }, files: new Set(), + }); + } + + const added = !base[file] && pr[file]; + const removed = base[file] && !pr[file]; + + acc.get(key).files.add({ + state: added ? 'added' : (removed ? 'removed' : 'changed'), + before: { size: beforeSize, sizeGz: beforeSizeGz }, + after: { size: afterSize, sizeGz: afterSizeGz }, + diffPercent: { + size: removed ? -100 : (added ? 100 : computeDiffPercent(beforeSize, afterSize)), + sizeGz: removed ? -100 : (added ? 100 : computeDiffPercent(beforeSizeGz, afterSizeGz)), + }, + meta: { + fileNameShort: file.replace(isBridge ? `src/${file.split('/')[1]}/src/Bridge/${file.split('/')[4]}/assets/dist/` : `src/${file.split('/')[1]}/assets/dist/`, ''), + fileNameUrl: `${repoUrl}/blob/${process.env.GITHUB_HEAD_REF}/${file}`, + }, + }); + } + + return acc; + }, new Map); + + if (packagesFiles.size === 0) { + output += 'ℹī¸ No difference in dist packagesFiles.\n'; + return output; + } + + output += 'Thanks for the PR! Here is the difference in size of the dist packagesFiles between the base and the PR.\n'; + output += 'Please review the changes and make sure they are expected.\n\n'; + output += ` + + `; + for (const [pkgKey, pkg] of packagesFiles.entries()) { + output += ``; + for (const file of pkg.files) { + output += ` + + `; + output += file.state === 'added' + ? `` + : ``; + output += file.state === 'removed' + ? `` + : ``; + output += ``; + } + } + output += ` +
FileBefore (Size / Gzip)After (Size / Gzip)
${pkgKey}
${file.meta.fileNameShort}Added + ${formatBytes(file.before.size)} + / ${formatBytes(file.before.sizeGz)} + Removed + ${formatBytes(file.after.size)}${file.state === 'changed' ? `${formatDiffPercent(file.diffPercent.size)}` : ''} + / ${formatBytes(file.after.sizeGz)}${file.state === 'changed' ? `${formatDiffPercent(file.diffPercent.sizeGz)}` : ''} +
+`; + + return output; +} + +if (!process.env.CI) { + console.log(main()); +} diff --git a/.github/workflows/dist-files-size-diff-comment.yaml b/.github/workflows/dist-files-size-diff-comment.yaml new file mode 100644 index 00000000000..d43f79c0a74 --- /dev/null +++ b/.github/workflows/dist-files-size-diff-comment.yaml @@ -0,0 +1,22 @@ +name: Dist Files Size Diff (Comment) + +on: + workflow_run: + workflows: ["Dist Files Size Diff"] + types: + - completed + +jobs: + dist-files-size-diff: + runs-on: ubuntu-latest + steps: + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: dist-size-${{ github.event.number }} + + - name: Comment on the pull request (if success) + if: ${{ always() && steps.diff.conclusion == 'success' }} + uses: marocchino/sticky-pull-request-comment@v2 + with: + path: ./dist-size.md diff --git a/.github/workflows/dist-files-size-diff.yaml b/.github/workflows/dist-files-size-diff.yaml new file mode 100644 index 00000000000..b690047decc --- /dev/null +++ b/.github/workflows/dist-files-size-diff.yaml @@ -0,0 +1,68 @@ +name: Dist Files Size Diff + +on: + pull_request: + types: [opened, synchronize] + paths: + - 'src/*/assets/dist/**' + - 'src/*/src/Bridge/*/assets/dist/**' + +jobs: + dist-files-size-diff: + runs-on: ubuntu-latest + steps: + - name: Configure git + run: | + git config --global user.email "" + git config --global user.name "github-action[bot]" + + - uses: actions/checkout@v4 + with: + ref: ${{ github.base_ref }} + + - name: Get dist files size (from base branch) + id: base-dist-files + run: | + set -e + + FILES=$(find src -mindepth 2 -path '*/assets/dist/*' \( -name "*.js" -o -name "*.css" \) -not \( -path '*/tests/*' -o -path '*/public/*' -o -path '*/vendor/*' \) | sort | while read -r file; do + echo "{\"$file\": {\"size\": $(wc -c < "$file"), \"size_gz\": $(gzip -c "$file" | wc -c)}}" + done | jq -s 'add' -c) + + echo "files=$FILES" >> $GITHUB_OUTPUT + + - uses: actions/checkout@v4 + + - name: Get dist files size (from pull request) + id: pr-dist-files + run: | + set -e + + FILES=$(find src -mindepth 2 -path '*/assets/dist/*' \( -name "*.js" -o -name "*.css" \) -not \( -path '*/tests/*' -o -path '*/public/*' -o -path '*/vendor/*' \) | sort | while read -r file; do + echo "{\"$file\": {\"size\": $(wc -c < "$file"), \"size_gz\": $(gzip -c "$file" | wc -c)}}" + done | jq -s 'add' -c) + + echo "files=$FILES" >> $GITHUB_OUTPUT + + - name: Generate the diff + id: diff + uses: actions/github-script@v7 + env: + BASE_DIST_FILES: ${{ steps.base-dist-files.outputs.files }} + PR_DIST_FILES: ${{ steps.pr-dist-files.outputs.files }} + with: + result-encoding: string + script: | + const fs = require('fs') + const { main } = await import('${{ github.workspace }}/.github/generate-dist-files-size-diff.mjs') + + const diff = await main() + console.log(diff); + + fs.writeFileSync(process.env.GITHUB_WORKSPACE + '/dist-size.md', diff) + + - name: Upload the diff + uses: actions/upload-artifact@v4 + with: + name: dist-size-${{ github.event.number }} + path: ./dist-size.md