1. 'use strict';
    
  2. 
    
  3. const {spawn} = require('child_process');
    
  4. const chalk = require('chalk');
    
  5. const yargs = require('yargs');
    
  6. const fs = require('fs');
    
  7. const path = require('path');
    
  8. const semver = require('semver');
    
  9. 
    
  10. const ossConfig = './scripts/jest/config.source.js';
    
  11. const wwwConfig = './scripts/jest/config.source-www.js';
    
  12. const devToolsConfig = './scripts/jest/config.build-devtools.js';
    
  13. 
    
  14. // TODO: These configs are separate but should be rolled into the configs above
    
  15. // so that the CLI can provide them as options for any of the configs.
    
  16. const persistentConfig = './scripts/jest/config.source-persistent.js';
    
  17. const buildConfig = './scripts/jest/config.build.js';
    
  18. 
    
  19. const argv = yargs
    
  20.   .parserConfiguration({
    
  21.     // Important: This option tells yargs to move all other options not
    
  22.     // specified here into the `_` key. We use this to send all of the
    
  23.     // Jest options that we don't use through to Jest (like --watch).
    
  24.     'unknown-options-as-args': true,
    
  25.   })
    
  26.   .wrap(yargs.terminalWidth())
    
  27.   .options({
    
  28.     debug: {
    
  29.       alias: 'd',
    
  30.       describe: 'Run with node debugger attached.',
    
  31.       requiresArg: false,
    
  32.       type: 'boolean',
    
  33.       default: false,
    
  34.     },
    
  35.     project: {
    
  36.       alias: 'p',
    
  37.       describe: 'Run the given project.',
    
  38.       requiresArg: true,
    
  39.       type: 'string',
    
  40.       default: 'default',
    
  41.       choices: ['default', 'devtools'],
    
  42.     },
    
  43.     releaseChannel: {
    
  44.       alias: 'r',
    
  45.       describe: 'Run with the given release channel.',
    
  46.       requiresArg: true,
    
  47.       type: 'string',
    
  48.       default: 'experimental',
    
  49.       choices: ['experimental', 'stable', 'www-classic', 'www-modern'],
    
  50.     },
    
  51.     env: {
    
  52.       alias: 'e',
    
  53.       describe: 'Run with the given node environment.',
    
  54.       requiresArg: true,
    
  55.       type: 'string',
    
  56.       choices: ['development', 'production'],
    
  57.     },
    
  58.     prod: {
    
  59.       describe: 'Run with NODE_ENV=production.',
    
  60.       requiresArg: false,
    
  61.       type: 'boolean',
    
  62.       default: false,
    
  63.     },
    
  64.     dev: {
    
  65.       describe: 'Run with NODE_ENV=development.',
    
  66.       requiresArg: false,
    
  67.       type: 'boolean',
    
  68.       default: false,
    
  69.     },
    
  70.     variant: {
    
  71.       alias: 'v',
    
  72.       describe: 'Run with www variant set to true.',
    
  73.       requiresArg: false,
    
  74.       type: 'boolean',
    
  75.     },
    
  76.     build: {
    
  77.       alias: 'b',
    
  78.       describe: 'Run tests on builds.',
    
  79.       requiresArg: false,
    
  80.       type: 'boolean',
    
  81.       default: false,
    
  82.     },
    
  83.     persistent: {
    
  84.       alias: 'n',
    
  85.       describe: 'Run with persistence.',
    
  86.       requiresArg: false,
    
  87.       type: 'boolean',
    
  88.       default: false,
    
  89.     },
    
  90.     ci: {
    
  91.       describe: 'Run tests in CI',
    
  92.       requiresArg: false,
    
  93.       type: 'boolean',
    
  94.       default: false,
    
  95.     },
    
  96.     deprecated: {
    
  97.       describe: 'Print deprecation message for command.',
    
  98.       requiresArg: true,
    
  99.       type: 'string',
    
  100.     },
    
  101.     compactConsole: {
    
  102.       alias: 'c',
    
  103.       describe: 'Compact console output (hide file locations).',
    
  104.       requiresArg: false,
    
  105.       type: 'boolean',
    
  106.       default: false,
    
  107.     },
    
  108.     reactVersion: {
    
  109.       describe: 'DevTools testing for specific version of React',
    
  110.       requiresArg: true,
    
  111.       type: 'string',
    
  112.     },
    
  113.     sourceMaps: {
    
  114.       describe:
    
  115.         'Enable inline source maps when transforming source files with Jest. Useful for debugging, but makes it slower.',
    
  116.       type: 'boolean',
    
  117.       default: false,
    
  118.     },
    
  119.   }).argv;
    
  120. 
    
  121. function logError(message) {
    
  122.   console.error(chalk.red(`\n${message}`));
    
  123. }
    
  124. function isWWWConfig() {
    
  125.   return (
    
  126.     (argv.releaseChannel === 'www-classic' ||
    
  127.       argv.releaseChannel === 'www-modern') &&
    
  128.     argv.project !== 'devtools'
    
  129.   );
    
  130. }
    
  131. 
    
  132. function isOSSConfig() {
    
  133.   return (
    
  134.     argv.releaseChannel === 'stable' || argv.releaseChannel === 'experimental'
    
  135.   );
    
  136. }
    
  137. 
    
  138. function validateOptions() {
    
  139.   let success = true;
    
  140. 
    
  141.   if (argv.project === 'devtools') {
    
  142.     if (argv.prod) {
    
  143.       logError(
    
  144.         'DevTool tests do not support --prod. Remove this option to continue.'
    
  145.       );
    
  146.       success = false;
    
  147.     }
    
  148. 
    
  149.     if (argv.dev) {
    
  150.       logError(
    
  151.         'DevTool tests do not support --dev. Remove this option to continue.'
    
  152.       );
    
  153.       success = false;
    
  154.     }
    
  155. 
    
  156.     if (argv.env) {
    
  157.       logError(
    
  158.         'DevTool tests do not support --env. Remove this option to continue.'
    
  159.       );
    
  160.       success = false;
    
  161.     }
    
  162. 
    
  163.     if (argv.persistent) {
    
  164.       logError(
    
  165.         'DevTool tests do not support --persistent. Remove this option to continue.'
    
  166.       );
    
  167.       success = false;
    
  168.     }
    
  169. 
    
  170.     if (argv.variant) {
    
  171.       logError(
    
  172.         'DevTool tests do not support --variant. Remove this option to continue.'
    
  173.       );
    
  174.       success = false;
    
  175.     }
    
  176. 
    
  177.     if (!argv.build) {
    
  178.       logError('DevTool tests require --build.');
    
  179.       success = false;
    
  180.     }
    
  181. 
    
  182.     if (argv.reactVersion && !semver.validRange(argv.reactVersion)) {
    
  183.       success = false;
    
  184.       logError('please specify a valid version range for --reactVersion');
    
  185.     }
    
  186.   } else {
    
  187.     if (argv.compactConsole) {
    
  188.       logError('Only DevTool tests support compactConsole flag.');
    
  189.       success = false;
    
  190.     }
    
  191.     if (argv.reactVersion) {
    
  192.       logError('Only DevTools tests supports the --reactVersion flag.');
    
  193.       success = false;
    
  194.     }
    
  195.   }
    
  196. 
    
  197.   if (isWWWConfig()) {
    
  198.     if (argv.variant === undefined) {
    
  199.       // Turn internal experiments on by default
    
  200.       argv.variant = true;
    
  201.     }
    
  202.   } else {
    
  203.     if (argv.variant) {
    
  204.       logError(
    
  205.         'Variant is only supported for the www release channels. Update these options to continue.'
    
  206.       );
    
  207.       success = false;
    
  208.     }
    
  209.   }
    
  210. 
    
  211.   if (argv.build && argv.persistent) {
    
  212.     logError(
    
  213.       'Persistence is not supported for build targets. Update these options to continue.'
    
  214.     );
    
  215.     success = false;
    
  216.   }
    
  217. 
    
  218.   if (!isOSSConfig() && argv.persistent) {
    
  219.     logError(
    
  220.       'Persistence only supported for oss release channels. Update these options to continue.'
    
  221.     );
    
  222.     success = false;
    
  223.   }
    
  224. 
    
  225.   if (argv.build && isWWWConfig()) {
    
  226.     logError(
    
  227.       'Build targets are only not supported for www release channels. Update these options to continue.'
    
  228.     );
    
  229.     success = false;
    
  230.   }
    
  231. 
    
  232.   if (argv.env && argv.env !== 'production' && argv.prod) {
    
  233.     logError(
    
  234.       'Build type does not match --prod. Update these options to continue.'
    
  235.     );
    
  236.     success = false;
    
  237.   }
    
  238. 
    
  239.   if (argv.env && argv.env !== 'development' && argv.dev) {
    
  240.     logError(
    
  241.       'Build type does not match --dev. Update these options to continue.'
    
  242.     );
    
  243.     success = false;
    
  244.   }
    
  245. 
    
  246.   if (argv.prod && argv.dev) {
    
  247.     logError(
    
  248.       'Cannot supply both --prod and --dev. Remove one of these options to continue.'
    
  249.     );
    
  250.     success = false;
    
  251.   }
    
  252. 
    
  253.   if (argv.build) {
    
  254.     // TODO: We could build this if it hasn't been built yet.
    
  255.     const buildDir = path.resolve('./build');
    
  256.     if (!fs.existsSync(buildDir)) {
    
  257.       logError(
    
  258.         'Build directory does not exist, please run `yarn build` or remove the --build option.'
    
  259.       );
    
  260.       success = false;
    
  261.     } else if (Date.now() - fs.statSync(buildDir).mtimeMs > 1000 * 60 * 15) {
    
  262.       logError(
    
  263.         'Warning: Running a build test with a build directory older than 15 minutes.\nPlease remember to run `yarn build` when using --build.'
    
  264.       );
    
  265.     }
    
  266.   }
    
  267. 
    
  268.   if (!success) {
    
  269.     console.log(''); // Extra newline.
    
  270.     process.exit(1);
    
  271.   }
    
  272. }
    
  273. 
    
  274. function getCommandArgs() {
    
  275.   // Add the correct Jest config.
    
  276.   const args = ['./scripts/jest/jest.js', '--config'];
    
  277.   if (argv.project === 'devtools') {
    
  278.     args.push(devToolsConfig);
    
  279.   } else if (argv.build) {
    
  280.     args.push(buildConfig);
    
  281.   } else if (argv.persistent) {
    
  282.     args.push(persistentConfig);
    
  283.   } else if (isWWWConfig()) {
    
  284.     args.push(wwwConfig);
    
  285.   } else if (isOSSConfig()) {
    
  286.     args.push(ossConfig);
    
  287.   } else {
    
  288.     // We should not get here.
    
  289.     logError('Unrecognized release channel');
    
  290.     process.exit(1);
    
  291.   }
    
  292. 
    
  293.   // Set the debug options, if necessary.
    
  294.   if (argv.debug) {
    
  295.     args.unshift('--inspect-brk');
    
  296.     args.push('--runInBand');
    
  297. 
    
  298.     // Prevent console logs from being hidden until test completes.
    
  299.     args.push('--useStderr');
    
  300.   }
    
  301. 
    
  302.   // CI Environments have limited workers.
    
  303.   if (argv.ci) {
    
  304.     args.push('--maxWorkers=2');
    
  305.   }
    
  306. 
    
  307.   // Push the remaining args onto the command.
    
  308.   // This will send args like `--watch` to Jest.
    
  309.   args.push(...argv._);
    
  310. 
    
  311.   return args;
    
  312. }
    
  313. 
    
  314. function getEnvars() {
    
  315.   const envars = {
    
  316.     NODE_ENV: argv.env || 'development',
    
  317.     RELEASE_CHANNEL: argv.releaseChannel.match(/modern|experimental/)
    
  318.       ? 'experimental'
    
  319.       : 'stable',
    
  320. 
    
  321.     // Pass this flag through to the config environment
    
  322.     // so the base config can conditionally load the console setup file.
    
  323.     compactConsole: argv.compactConsole,
    
  324.   };
    
  325. 
    
  326.   if (argv.prod) {
    
  327.     envars.NODE_ENV = 'production';
    
  328.   }
    
  329. 
    
  330.   if (argv.dev) {
    
  331.     envars.NODE_ENV = 'development';
    
  332.   }
    
  333. 
    
  334.   if (argv.variant) {
    
  335.     envars.VARIANT = true;
    
  336.   }
    
  337. 
    
  338.   if (argv.reactVersion) {
    
  339.     envars.REACT_VERSION = semver.coerce(argv.reactVersion);
    
  340.   }
    
  341. 
    
  342.   if (argv.sourceMaps) {
    
  343.     // This is off by default because it slows down the test runner, but it's
    
  344.     // super useful when running the debugger.
    
  345.     envars.JEST_ENABLE_SOURCE_MAPS = 'inline';
    
  346.   }
    
  347. 
    
  348.   return envars;
    
  349. }
    
  350. 
    
  351. function main() {
    
  352.   if (argv.deprecated) {
    
  353.     console.log(chalk.red(`\nPlease run: \`${argv.deprecated}\` instead.\n`));
    
  354.     return;
    
  355.   }
    
  356. 
    
  357.   validateOptions();
    
  358. 
    
  359.   const args = getCommandArgs();
    
  360.   const envars = getEnvars();
    
  361.   const env = Object.entries(envars).map(([k, v]) => `${k}=${v}`);
    
  362. 
    
  363.   // Print the full command we're actually running.
    
  364.   const command = `$ ${env.join(' ')} node ${args.join(' ')}`;
    
  365.   console.log(chalk.dim(command));
    
  366. 
    
  367.   // Print the release channel and project we're running for quick confirmation.
    
  368.   console.log(
    
  369.     chalk.blue(
    
  370.       `\nRunning tests for ${argv.project} (${argv.releaseChannel})...`
    
  371.     )
    
  372.   );
    
  373. 
    
  374.   // Print a message that the debugger is starting just
    
  375.   // for some extra feedback when running the debugger.
    
  376.   if (argv.debug) {
    
  377.     console.log(chalk.green('\nStarting debugger...'));
    
  378.     console.log(chalk.green('Open chrome://inspect and press "inspect"\n'));
    
  379.   }
    
  380. 
    
  381.   // Run Jest.
    
  382.   const jest = spawn('node', args, {
    
  383.     stdio: 'inherit',
    
  384.     env: {...envars, ...process.env},
    
  385.   });
    
  386. 
    
  387.   // Ensure we close our process when we get a failure case.
    
  388.   jest.on('close', code => {
    
  389.     // Forward the exit code from the Jest process.
    
  390.     if (code === 1) {
    
  391.       process.exit(1);
    
  392.     }
    
  393.   });
    
  394. }
    
  395. 
    
  396. main();