Frames

Untitled

0
1
1/**
2 * Copyright (c) Facebook, Inc. and its affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8'use strict';
9
10// Hi, if this is your first time editing/reading a Dangerfile, here's a summary:
11// It's a JS runtime which helps you provide continuous feedback inside GitHub.
12//
13// You can see the docs here: http://danger.systems/js/
14//
15// If you want to test changes Danger, I'd recommend checking out an existing PR
16// and then running the `danger pr` command.
17//
18// You'll need a GitHub token, you can re-use this one:
19//
20// 0a7d5c3cad9a6dbec2d9 9a5222cf49062a4c1ef7
21//
22// (Just remove the space)
23//
24// So, for example:
25//
26// `DANGER_GITHUB_API_TOKEN=[ENV_ABOVE] yarn danger pr https://github.com/facebook/react/pull/11865
27
28const {markdown, danger, warn} = require('danger');
29const fetch = require('node-fetch');
30
31const {generateResultsArray} = require('./scripts/rollup/stats');
32const {existsSync, readFileSync} = require('fs');
33const {exec} = require('child_process');
34
35if (!existsSync('./build/bundle-sizes.json')) {
36 // This indicates the build failed previously.
37 // In that case, there's nothing for the Dangerfile to do.
38 // Exit early to avoid leaving a redundant (and potentially confusing) PR comment.
39 warn(
40 'No bundle size information found. This indicates the build ' +
41 'job failed.'
42 );
43 process.exit(0);
44}
45
46const currentBuildResults = JSON.parse(
47 readFileSync('./build/bundle-sizes.json')
48);
49
50/**
51 * Generates a Markdown table
52 * @param {string[]} headers
53 * @param {string[][]} body
54 */
55function generateMDTable(headers, body) {
56 const tableHeaders = [
57 headers.join(' | '),
58 headers.map(() => ' --- ').join(' | '),
59 ];
60
61 const tablebody = body.map(r => r.join(' | '));
62 return tableHeaders.join('\n') + '\n' + tablebody.join('\n');
63}
64
65/**
66 * Generates a user-readable string from a percentage change
67 * @param {number} change
68 * @param {boolean} includeEmoji
69 */
70function addPercent(change, includeEmoji) {
71 if (!isFinite(change)) {
72 // When a new package is created
73 return 'n/a';
74 }
75 const formatted = (change * 100).toFixed(1);
76 if (/^-|^0(?:\.0+)$/.test(formatted)) {
77 return `${formatted}%`;
78 } else {
79 if (includeEmoji) {
80 return `:small_red_triangle:+${formatted}%`;
81 } else {
82 return `+${formatted}%`;
83 }
84 }
85}
86
87function setBoldness(row, isBold) {
88 if (isBold) {
89 return row.map(element => `**${element}**`);
90 } else {
91 return row;
92 }
93}
94
95/**
96 * Gets the commit that represents the merge between the current branch
97 * and master.
98 */
99function git(args) {
100 return new Promise(res => {
101 exec('git ' + args, (err, stdout, stderr) => {
102 if (err) {
103 throw err;
104 } else {
105 res(stdout.trim());
106 }
107 });
108 });
109}
110
111(async function() {
112 // Use git locally to grab the commit which represents the place
113 // where the branches differ
114 const upstreamRepo = danger.github.pr.base.repo.full_name;
115 if (upstreamRepo !== 'facebook/react') {
116 // Exit unless we're running in the main repo
117 return;
118 }
119
120 const upstreamRef = danger.github.pr.base.ref;
121 await git(`remote add upstream https://github.com/facebook/react.git`);
122 await git('fetch upstream');
123 const baseCommit = await git(`merge-base HEAD upstream/${upstreamRef}`);
124
125 let previousBuildResults = null;
126 try {
127 let baseCIBuildId = null;
128 const statusesResponse = await fetch(
129 `https://api.github.com/repos/facebook/react/commits/${baseCommit}/status`
130 );
131 const {statuses, state} = await statusesResponse.json();
132 if (state === 'failure') {
133 warn(`Base commit is broken: ${baseCommit}`);
134 return;
135 }
136 for (let i = 0; i < statuses.length; i++) {
137 const status = statuses[i];
138 // This must match the name of the CI job that creates the build artifacts
139 if (status.context === 'ci/circleci: process_artifacts') {
140 if (status.state === 'success') {
141 baseCIBuildId = /\/facebook\/react\/([0-9]+)/.exec(
142 status.target_url
143 )[1];
144 break;
145 }
146 if (status.state === 'pending') {
147 warn(`Build job for base commit is still pending: ${baseCommit}`);
148 return;
149 }
150 }
151 }
152
153 if (baseCIBuildId === null) {
154 warn(`Could not find build artifacts for base commit: ${baseCommit}`);
155 return;
156 }
157
158 const baseArtifactsInfoResponse = await fetch(
159 `https://circleci.com/api/v1.1/project/github/facebook/react/${baseCIBuildId}/artifacts`
160 );
161 const baseArtifactsInfo = await baseArtifactsInfoResponse.json();
162
163 for (let i = 0; i < baseArtifactsInfo.length; i++) {
164 const info = baseArtifactsInfo[i];
165 if (info.path === 'home/circleci/project/build/bundle-sizes.json') {
166 const resultsResponse = await fetch(info.url);
167 previousBuildResults = await resultsResponse.json();
168 break;
169 }
170 }
171 } catch (error) {
172 warn(`Failed to fetch build artifacts for base commit: ${baseCommit}`);
173 return;
174 }
175
176 if (previousBuildResults === null) {
177 warn(`Could not find build artifacts for base commit: ${baseCommit}`);
178 return;
179 }
180
181 // Take the JSON of the build response and
182 // make an array comparing the results for printing
183 const results = generateResultsArray(
184 currentBuildResults,
185 previousBuildResults
186 );
187
188 const packagesToShow = results
189 .filter(
190 r =>
191 Math.abs(r.prevFileSizeAbsoluteChange) >= 300 || // bytes
192 Math.abs(r.prevGzipSizeAbsoluteChange) >= 100 // bytes
193 )
194 .map(r => r.packageName);
195
196 if (packagesToShow.length) {
197 let allTables = [];
198
199 // Highlight React and React DOM changes inline
200 // e.g. react: `react.production.min.js`: -3%, `react.development.js`: +4%
201
202 if (packagesToShow.includes('react')) {
203 const reactProd = results.find(
204 r => r.bundleType === 'UMD_PROD' && r.packageName === 'react'
205 );
206 if (
207 reactProd.prevFileSizeChange !== 0 ||
208 reactProd.prevGzipSizeChange !== 0
209 ) {
210 const changeSize = addPercent(reactProd.prevFileSizeChange, true);
211 const changeGzip = addPercent(reactProd.prevGzipSizeChange, true);
212 markdown(`React: size: ${changeSize}, gzip: ${changeGzip}`);
213 }
214 }
215
216 if (packagesToShow.includes('react-dom')) {
217 const reactDOMProd = results.find(
218 r => r.bundleType === 'UMD_PROD' && r.packageName === 'react-dom'
219 );
220 if (
221 reactDOMProd.prevFileSizeChange !== 0 ||
222 reactDOMProd.prevGzipSizeChange !== 0
223 ) {
224 const changeSize = addPercent(reactDOMProd.prevFileSizeChange, true);
225 const changeGzip = addPercent(reactDOMProd.prevGzipSizeChange, true);
226 markdown(`ReactDOM: size: ${changeSize}, gzip: ${changeGzip}`);
227 }
228 }
229
230 // Show a hidden summary table for all diffs
231
232 // eslint-disable-next-line no-var,no-for-of-loops/no-for-of-loops
233 for (var name of new Set(packagesToShow)) {
234 const thisBundleResults = results.filter(r => r.packageName === name);
235 const changedFiles = thisBundleResults.filter(
236 r => r.prevFileSizeChange !== 0 || r.prevGzipSizeChange !== 0
237 );
238
239 const mdHeaders = [
240 'File',
241 'Filesize Diff',
242 'Gzip Diff',
243 'Prev Size',
244 'Current Size',
245 'Prev Gzip',
246 'Current Gzip',
247 'ENV',
248 ];
249
250 const mdRows = changedFiles.map(r => {
251 const isProd = r.bundleType.includes('PROD');
252 return setBoldness(
253 [
254 r.filename,
255 addPercent(r.prevFileSizeChange, isProd),
256 addPercent(r.prevGzipSizeChange, isProd),
257 r.prevSize,
258 r.prevFileSize,
259 r.prevGzip,
260 r.prevGzipSize,
261 r.bundleType,
262 ],
263 isProd
264 );
265 });
266
267 allTables.push(`\n## ${name}`);
268 allTables.push(generateMDTable(mdHeaders, mdRows));
269 }
270
271 const summary = `
272 <details>
273 <summary>Details of bundled changes.</summary>
274
275 <p>Comparing: ${baseCommit}...${danger.github.pr.head.sha}</p>
276
277
278 ${allTables.join('\n')}
279
280 </details>
281 `;
282 markdown(summary);
283 } else {
284 markdown('No significant bundle size changes to report.');
285 }
286})();
287