Adds Danger and a rule showing build size differences (#11865)

* Adds danger_js with an initial rule for warning about large PRs

Signed-off-by: Anandaroop Roy <roop@artsymail.com>

* [WIP] Get the before and after for the build results

* [Dev] More work on the Dangerfile

* [Danger] Split the reports into sections based on their package

* Remove the --extract-errors on the circle build

* [Danger] Improve the lookup for previous -> current build to also include the environment

* Fix rebase
This commit is contained in:
Orta
2018-01-17 01:49:38 +00:00
committed by Dan Abramov
parent 4f309f86df
commit d8d797645c
7 changed files with 862 additions and 87 deletions
+151
View File
@@ -0,0 +1,151 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
const {markdown, danger} = require('danger');
const fetch = require('node-fetch');
const {generateResultsArray} = require('./scripts/rollup/stats');
const {readFileSync} = require('fs');
const currentBuildResults = JSON.parse(
readFileSync('./scripts/rollup/results.json')
);
/**
* Generates a Markdown table
* @param {string[]} headers
* @param {string[][]} body
*/
function generateMDTable(headers, body) {
const tableHeaders = [
headers.join(' | '),
headers.map(() => ' --- ').join(' | '),
];
const tablebody = body.map(r => r.join(' | '));
return tableHeaders.join('\n') + '\n' + tablebody.join('\n');
}
/**
* Generates a user-readable string from a percentage change
* @param {string[]} headers
*/
function emojiPercent(change) {
if (change > 0) {
return `:small_red_triangle:+${change}%`;
} else if (change <= 0) {
return `${change}%`;
}
}
// Grab the results.json before we ran CI via the GH API
// const baseMerge = danger.github.pr.base.sha
const parentOfOldestCommit = danger.git.commits[0].parents[0];
const commitURL = sha =>
`http://react.zpao.com/builds/master/_commits/${sha}/results.json`;
fetch(commitURL(parentOfOldestCommit)).then(async response => {
const previousBuildResults = await response.json();
const results = generateResultsArray(
currentBuildResults,
previousBuildResults
);
const percentToWarrentShowing = 1;
const packagesToShow = results
.filter(
r =>
Math.abs(r.prevFileSizeChange) > percentToWarrentShowing ||
Math.abs(r.prevGzipSizeChange) > percentToWarrentShowing
)
.map(r => r.packageName);
if (packagesToShow.length) {
let allTables = [];
// Highlight React and React DOM changes inline
// e.g. react: `react.production.min.js`: -3%, `react.development.js`: +4%
if (packagesToShow.includes('react')) {
const reactProd = results.find(
r => r.bundleType === 'UMD_PROD' && r.packageName === 'react'
);
if (
reactProd.prevFileSizeChange !== 0 ||
reactProd.prevGzipSizeChange !== 0
) {
const changeSize = emojiPercent(reactProd.prevFileSizeChange);
const changeGzip = emojiPercent(reactProd.prevGzipSizeChange);
markdown(`React: size: ${changeSize}, gzip: ${changeGzip}`);
}
}
if (packagesToShow.includes('react-dom')) {
const reactDOMProd = results.find(
r => r.bundleType === 'UMD_PROD' && r.packageName === 'react-dom'
);
if (
reactDOMProd.prevFileSizeChange !== 0 ||
reactDOMProd.prevGzipSizeChange !== 0
) {
const changeSize = emojiPercent(reactDOMProd.prevFileSizeChange);
const changeGzip = emojiPercent(reactDOMProd.prevGzipSizeChange);
markdown(`ReactDOM: size: ${changeSize}, gzip: ${changeGzip}`);
}
}
// Show a hidden summary table for all diffs
// eslint-disable-next-line no-var
for (var name of new Set(packagesToShow)) {
const thisBundleResults = results.filter(r => r.packageName === name);
const changedFiles = thisBundleResults.filter(
r => r.prevGzipSizeChange !== 0 || r.prevGzipSizeChange !== 0
);
const mdHeaders = [
'File',
'Filesize Diff',
'Gzip Diff',
'Prev Size',
'Current Size',
'Prev Gzip',
'Current Gzip',
'ENV',
];
const mdRows = changedFiles.map(r => [
r.filename,
emojiPercent(r.prevFileSizeChange),
emojiPercent(r.prevGzipSizeChange),
r.prevSize,
r.prevFileSize,
r.prevGzip,
r.prevGzipSize,
r.bundleType,
]);
allTables.push(`\n## ${name}`);
allTables.push(generateMDTable(mdHeaders, mdRows));
}
const summary = `
<details>
<summary>Details of bundled changes.</summary>
<p>Comparing: ${parentOfOldestCommit}...${danger.github.pr.head.sha}</p>
${allTables.join('\n')}
</details>
`;
markdown(summary);
}
});
+1
View File
@@ -47,6 +47,7 @@
"coveralls": "^2.11.6",
"create-react-class": "^15.6.2",
"cross-env": "^5.1.1",
"danger": "^3.0.4",
"del": "^2.0.2",
"derequire": "^2.0.3",
"escape-string-regexp": "^1.0.5",
+1
View File
@@ -25,6 +25,7 @@ if [ $((2 % CIRCLE_NODE_TOTAL)) -eq "$CIRCLE_NODE_INDEX" ]; then
COMMANDS_TO_RUN+=('./scripts/circleci/build.sh')
COMMANDS_TO_RUN+=('yarn test-build --maxWorkers=2')
COMMANDS_TO_RUN+=('yarn test-build-prod --maxWorkers=2')
COMMANDS_TO_RUN+=('node ./scripts/tasks/danger')
COMMANDS_TO_RUN+=('./scripts/circleci/upload_build.sh')
fi
+1 -1
View File
@@ -400,4 +400,4 @@
"gzip": 525
}
]
}
}
+61 -35
View File
@@ -21,8 +21,10 @@ function saveResults() {
}
function percentChange(prev, current) {
const change = Math.floor((current - prev) / prev * 100);
return Math.floor((current - prev) / prev * 100);
}
function percentChangeString(change) {
if (change > 0) {
return chalk.red.bold(`+${change} %`);
} else if (change <= 0) {
@@ -30,51 +32,75 @@ function percentChange(prev, current) {
}
}
const resultsHeaders = [
'Bundle',
'Prev Size',
'Current Size',
'Diff',
'Prev Gzip',
'Current Gzip',
'Diff',
];
function generateResultsArray(current, prevResults) {
return current.bundleSizes
.map(result => {
const prev = prevResults.bundleSizes.filter(
res =>
res.filename === result.filename &&
res.bundleType === result.bundleType
)[0];
if (result === prev) {
// We didn't rebuild this bundle.
return;
}
const size = result.size;
const gzip = result.gzip;
let prevSize = prev ? prev.size : 0;
let prevGzip = prev ? prev.gzip : 0;
return {
filename: result.filename,
bundleType: result.bundleType,
packageName: result.packageName,
prevSize: filesize(prevSize),
prevFileSize: filesize(size),
prevFileSizeChange: percentChange(prevSize, size),
prevGzip: filesize(prevGzip),
prevGzipSize: filesize(gzip),
prevGzipSizeChange: percentChange(prevGzip, gzip),
};
// Strip any nulls
})
.filter(f => f);
}
function printResults() {
const table = new Table({
head: [
chalk.gray.yellow('Bundle'),
chalk.gray.yellow('Prev Size'),
chalk.gray.yellow('Current Size'),
chalk.gray.yellow('Diff'),
chalk.gray.yellow('Prev Gzip'),
chalk.gray.yellow('Current Gzip'),
chalk.gray.yellow('Diff'),
],
head: resultsHeaders.map(chalk.gray.yellow),
});
currentBuildResults.bundleSizes.forEach(result => {
const matches = prevBuildResults.bundleSizes.filter(
({filename, bundleType}) =>
filename === result.filename && bundleType === result.bundleType
);
if (matches.length > 1) {
throw new Error(`Ambiguous bundle size record for: ${result.filename}`);
}
const prev = matches[0];
if (result === prev) {
// We didn't rebuild this bundle.
return;
}
const size = result.size;
const gzip = result.gzip;
let prevSize = prev ? prev.size : 0;
let prevGzip = prev ? prev.gzip : 0;
const results = generateResultsArray(currentBuildResults, prevBuildResults);
results.forEach(result => {
table.push([
chalk.white.bold(`${result.filename} (${result.bundleType})`),
chalk.gray.bold(filesize(prevSize)),
chalk.white.bold(filesize(size)),
percentChange(prevSize, size),
chalk.gray.bold(filesize(prevGzip)),
chalk.white.bold(filesize(gzip)),
percentChange(prevGzip, gzip),
chalk.white.bold(`${result.filename} (${result.bundleType})`),
chalk.gray.bold(result.prevSize),
chalk.white.bold(result.prevFileSize),
percentChangeString(result.prevFileSizeChange),
chalk.gray.bold(result.prevGzip),
chalk.white.bold(result.prevGzipSize),
percentChangeString(result.prevGzipSizeChange),
]);
});
return table.toString();
}
module.exports = {
currentBuildResults,
generateResultsArray,
printResults,
saveResults,
currentBuildResults,
resultsHeaders,
};
+32
View File
@@ -0,0 +1,32 @@
/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
const path = require('path');
const spawn = require('child_process').spawn;
const extension = process.platform === 'win32' ? '.cmd' : '';
// This came from React Native's circle.yml
const token = 'e622517d9f1136ea8900' + '07c6373666312cdfaa69';
spawn(path.join('node_modules', '.bin', 'danger-ci' + extension), [], {
// Allow colors to pass through
stdio: 'inherit',
env: {
...process.env,
DANGER_GITHUB_API_TOKEN: token,
},
}).on('close', function(code) {
if (code !== 0) {
console.error('Danger failed');
} else {
console.log('Danger passed');
}
process.exit(code);
});
+615 -51
View File
File diff suppressed because it is too large Load Diff