dangerfile.js JAVASCRIPT 283 lines View on github.com → Search inside
1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 */78'use strict';910/* eslint-disable no-for-of-loops/no-for-of-loops */1112// Hi, if this is your first time editing/reading a Dangerfile, here's a summary:13// It's a JS runtime which helps you provide continuous feedback inside GitHub.14//15// You can see the docs here: http://danger.systems/js/16//17// If you want to test changes Danger, I'd recommend checking out an existing PR18// and then running the `danger pr` command.19//20// You'll need a GitHub token, you can re-use this one:21//22//  0a7d5c3cad9a6dbec2d9 9a5222cf49062a4c1ef723//24// (Just remove the space)25//26// So, for example:27//28// `DANGER_GITHUB_API_TOKEN=[ENV_ABOVE] yarn danger pr https://github.com/facebook/react/pull/118652930const {markdown, danger, warn} = require('danger');31const {promisify} = require('util');32const glob = promisify(require('glob'));33const gzipSize = require('gzip-size');34const {writeFileSync} = require('fs');3536const {readFileSync, statSync} = require('fs');3738const BASE_DIR = 'base-build';39const HEAD_DIR = 'build';4041const CRITICAL_THRESHOLD = 0.02;42const SIGNIFICANCE_THRESHOLD = 0.002;43const CRITICAL_ARTIFACT_PATHS = new Set([44  // We always report changes to these bundles, even if the change is45  // insignificant or non-existent.46  'oss-stable/react-dom/cjs/react-dom.production.js',47  'oss-stable/react-dom/cjs/react-dom-client.production.js',48  'oss-experimental/react-dom/cjs/react-dom.production.js',49  'oss-experimental/react-dom/cjs/react-dom-client.production.js',50  'facebook-www/ReactDOM-prod.classic.js',51  'facebook-www/ReactDOM-prod.modern.js',52]);5354const kilobyteFormatter = new Intl.NumberFormat('en', {55  style: 'unit',56  unit: 'kilobyte',57  minimumFractionDigits: 2,58  maximumFractionDigits: 2,59});6061function kbs(bytes) {62  return kilobyteFormatter.format(bytes / 1000);63}6465const percentFormatter = new Intl.NumberFormat('en', {66  style: 'percent',67  signDisplay: 'exceptZero',68  minimumFractionDigits: 2,69  maximumFractionDigits: 2,70});7172function change(decimal) {73  if (decimal === Infinity) {74    return 'New file';75  }76  if (decimal === -1) {77    return 'Deleted';78  }79  if (decimal < 0.0001) {80    return '=';81  }82  return percentFormatter.format(decimal);83}8485const header = `86  | Name | +/- | Base | Current | +/- gzip | Base gzip | Current gzip |87  | ---- | --- | ---- | ------- | -------- | --------- | ------------ |`;8889function row(result, baseSha, headSha) {90  const diffViewUrl = `https://react-builds.vercel.app/commits/${headSha}/files/${result.path}?compare=${baseSha}`;91  const rowArr = [92    `| [${result.path}](${diffViewUrl})`,93    `**${change(result.change)}**`,94    `${kbs(result.baseSize)}`,95    `${kbs(result.headSize)}`,96    `${change(result.changeGzip)}`,97    `${kbs(result.baseSizeGzip)}`,98    `${kbs(result.headSizeGzip)}`,99  ];100  return rowArr.join(' | ');101}102103(async function () {104  // Use git locally to grab the commit which represents the place105  // where the branches differ106107  const upstreamRepo = danger.github.pr.base.repo.full_name;108  if (upstreamRepo !== 'react/react') {109    // Exit unless we're running in the main repo110    return;111  }112113  let headSha;114  let baseSha;115  try {116    headSha = String(readFileSync(HEAD_DIR + '/COMMIT_SHA')).trim();117    baseSha = String(readFileSync(BASE_DIR + '/COMMIT_SHA')).trim();118  } catch {119    warn(120      "Failed to read build artifacts. It's possible a build configuration " +121        'has changed upstream. Try pulling the latest changes from the ' +122        'main branch.'123    );124    return;125  }126127  // Disable sizeBot in a Devtools Pull Request. Because that doesn't affect production bundle size.128  const commitFiles = [129    ...danger.git.created_files,130    ...danger.git.deleted_files,131    ...danger.git.modified_files,132  ];133  if (134    commitFiles.every(filename => filename.includes('packages/react-devtools'))135  )136    return;137138  const resultsMap = new Map();139140  // Find all the head (current) artifacts paths.141  const headArtifactPaths = await glob('**/*.js', {cwd: 'build'});142  for (const artifactPath of headArtifactPaths) {143    try {144      // This will throw if there's no matching base artifact145      const baseSize = statSync(BASE_DIR + '/' + artifactPath).size;146      const baseSizeGzip = gzipSize.fileSync(BASE_DIR + '/' + artifactPath);147148      const headSize = statSync(HEAD_DIR + '/' + artifactPath).size;149      const headSizeGzip = gzipSize.fileSync(HEAD_DIR + '/' + artifactPath);150      resultsMap.set(artifactPath, {151        path: artifactPath,152        headSize,153        headSizeGzip,154        baseSize,155        baseSizeGzip,156        change: (headSize - baseSize) / baseSize,157        changeGzip: (headSizeGzip - baseSizeGzip) / baseSizeGzip,158      });159    } catch {160      // There's no matching base artifact. This is a new file.161      const baseSize = 0;162      const baseSizeGzip = 0;163      const headSize = statSync(HEAD_DIR + '/' + artifactPath).size;164      const headSizeGzip = gzipSize.fileSync(HEAD_DIR + '/' + artifactPath);165      resultsMap.set(artifactPath, {166        path: artifactPath,167        headSize,168        headSizeGzip,169        baseSize,170        baseSizeGzip,171        change: Infinity,172        changeGzip: Infinity,173      });174    }175  }176177  // Check for base artifacts that were deleted in the head.178  const baseArtifactPaths = await glob('**/*.js', {cwd: 'base-build'});179  for (const artifactPath of baseArtifactPaths) {180    if (!resultsMap.has(artifactPath)) {181      const baseSize = statSync(BASE_DIR + '/' + artifactPath).size;182      const baseSizeGzip = gzipSize.fileSync(BASE_DIR + '/' + artifactPath);183      const headSize = 0;184      const headSizeGzip = 0;185      resultsMap.set(artifactPath, {186        path: artifactPath,187        headSize,188        headSizeGzip,189        baseSize,190        baseSizeGzip,191        change: -1,192        changeGzip: -1,193      });194    }195  }196197  const results = Array.from(resultsMap.values());198  results.sort((a, b) => b.change - a.change);199200  let criticalResults = [];201  for (const artifactPath of CRITICAL_ARTIFACT_PATHS) {202    const result = resultsMap.get(artifactPath);203    if (result === undefined) {204      throw new Error(205        'Missing expected bundle. If this was an intentional change to the ' +206          'build configuration, update Dangerfile.js accordingly: ' +207          artifactPath208      );209    }210    criticalResults.push(row(result, baseSha, headSha));211  }212213  let significantResults = [];214  for (const result of results) {215    // If result exceeds critical threshold, add to top section.216    if (217      (result.change > CRITICAL_THRESHOLD ||218        0 - result.change > CRITICAL_THRESHOLD ||219        // New file220        result.change === Infinity ||221        // Deleted file222        result.change === -1) &&223      // Skip critical artifacts. We added those earlier, in a fixed order.224      !CRITICAL_ARTIFACT_PATHS.has(result.path)225    ) {226      criticalResults.push(row(result, baseSha, headSha));227    }228229    // Do the same for results that exceed the significant threshold. These230    // will go into the bottom, collapsed section. Intentionally including231    // critical artifacts in this section, too.232    if (233      result.change > SIGNIFICANCE_THRESHOLD ||234      0 - result.change > SIGNIFICANCE_THRESHOLD ||235      result.change === Infinity ||236      result.change === -1237    ) {238      significantResults.push(row(result, baseSha, headSha));239    }240  }241242  const message = `243Comparing: ${baseSha}...${headSha}244245## Critical size changes246247Includes critical production bundles, as well as any change greater than ${248    CRITICAL_THRESHOLD * 100249  }%:250251${header}252${criticalResults.join('\n')}253254## Significant size changes255256Includes any change greater than ${SIGNIFICANCE_THRESHOLD * 100}%:257258${259  significantResults.length > 0260    ? `261<details>262<summary>Expand to show</summary>263${header}264${significantResults.join('\n')}265</details>266`267    : '(No significant changes)'268}269`;270271  // GitHub comments are limited to 65536 characters.272  if (message.length > 65536) {273    // Make message available as an artifact274    writeFileSync('sizebot-message.md', message);275    markdown(276      'The size diff is too large to display in a single comment. ' +277        `The GitHub action for this pull request contains an artifact called 'sizebot-message.md' with the full message.`278    );279  } else {280    markdown(message);281  }282})();

Code quality findings 10

Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (decimal === Infinity) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (decimal === -1) {
Ensure all async functions handle errors properly
info correctness async-without-catch
(async function () {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (upstreamRepo !== 'react/react') {
Ensure try blocks have corresponding catch or finally blocks
info correctness try-without-catch
try {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
if (result === undefined) {
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
result.change === Infinity ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
result.change === -1) &&
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
result.change === Infinity ||
Use strict equality (===) to prevent type coercion bugs
info correctness loose-equality
result.change === -1

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.