<commit_before>import fs from 'fs';
import os from 'os';
import path from 'path';
import readline from 'readline';
import { performance } from 'perf_hooks';

import JSDOMDomProvider from './JSDOMDomProvider';
import Logger from './Logger';
import MultipleErrors from './MultipleErrors';
import constructReport from './constructReport';
import createStaticPackage from './createStaticPackage';
import createDynamicEntryPoint from './createDynamicEntryPoint';
import createWebpackBundle from './createWebpackBundle';
import loadCSSFile from './loadCSSFile';
import prepareAssetsPackage from './prepareAssetsPackage';
import processSnapsInBundle from './processSnapsInBundle';

const { VERBOSE = 'false' } = process.env;

function logTargetResults({ name, globalCSS, snapPayloads }) {
  const cssPath = path.join(os.tmpdir(), `happo-verbose-${name}.css`);
  const snippetsPath = path.join(os.tmpdir(), `happo-snippets-${name}.json`);
  fs.writeFileSync(cssPath, JSON.stringify(globalCSS));
  fs.writeFileSync(snippetsPath, JSON.stringify(snapPayloads));
  console.log(`Recorded CSS for target "${name}" can be found in ${cssPath}`);
  console.log(
    `Recorded HTML snippets for target "${name}" can be found in ${snippetsPath}`,
  );
}

function waitForAnyKey() {
  readline.emitKeypressEvents(process.stdin);
  process.stdin.setRawMode(true);
  process.stdin.resume();

  return new Promise((resolve) => {
    process.stdin.once('keypress', (_, key) => {
      if (key.ctrl && key.name === 'c') {
        process.exit();
      } else {
        process.stdin.setRawMode(false);
        process.stdin.pause();
        resolve();
      }
    });
  });
}

function resolveDomProvider({ plugins, jsdomOptions }) {
  const pluginWithProvider = plugins.find(({ DomProvider }) => !!DomProvider);
  if (pluginWithProvider) {
    return pluginWithProvider.DomProvider;
  }
  return JSDOMDomProvider.bind(JSDOMDomProvider, jsdomOptions);
}

async function executeTargetWithPrerender({
  name,
  targets,
  css,
  snapPayloads,
  publicFolders,
  cssBlocks,
  apiKey,
  apiSecret,
  endpoint,
  isAsync,
}) {
  if (!snapPayloads.length) {
    console.warn(`No examples found for target ${name}, skipping`);
    return [];
  }
  const errors = snapPayloads.filter((p) => p.isError);
  if (errors.length === 1) {
    throw errors[0];
  }
  if (errors.length > 1) {
    throw new MultipleErrors(errors);
  }

  const globalCSS = cssBlocks.concat([{ css }]);

  if (VERBOSE === 'true') {
    logTargetResults({ name, globalCSS, snapPayloads });
  }

  const assetsPackage = await prepareAssetsPackage({
    globalCSS,
    snapPayloads,
    publicFolders,
  });
  snapPayloads.forEach((item) => {
    delete item.assetPaths;
  });

  const result = await targets[name].execute({
    asyncResults: isAsync,
    targetName: name,
    assetsPackage,
    globalCSS,
    snapPayloads,
    apiKey,
    apiSecret,
    endpoint,
  });
  return result;
}

async function generateScreenshots(
  {
    apiKey,
    apiSecret,
    stylesheets,
    endpoint,
    targets,
    publicFolders,
    jsdomOptions,
    plugins,
    prerender,
    tmpdir,
    isAsync,
  },
  bundleFile,
  logger,
) {
  const cssBlocks = await Promise.all(
    stylesheets.map(async (sheet) => {
      const { source, id, conditional } =
        typeof sheet === 'string' ? { source: sheet } : sheet;
      const result = {
        source,
        css: await loadCSSFile(source),
      };
      if (id) result.id = id;
      if (conditional) result.conditional = conditional;
      return result;
    }),
  );
  plugins.forEach(({ css }) => {
    if (css) {
      cssBlocks.push({ css });
    }
  });

  const targetNames = Object.keys(targets);
  const tl = targetNames.length;
  const DomProvider = resolveDomProvider({ plugins, jsdomOptions });
  logger.info(`Generating screenshots in ${tl} target${tl > 1 ? 's' : ''}...`);
  try {
    const staticPackage = prerender
      ? undefined
      : await createStaticPackage({
        tmpdir,
        publicFolders,
      });

    let results;
    if (prerender) {
      const processedSnaps = {};
      for (const name of targetNames) {
        // These tasks are CPU-bound, and we need to be careful about how much
        // memory we are using at one time, so we want to run them serially.
        // eslint-disable-next-line no-await-in-loop
        const { css, snapPayloads } = await processSnapsInBundle(bundleFile, {
          targetName: name,
          publicFolders,
          viewport: targets[name].viewport,
          DomProvider,
        });
        processedSnaps[name] = { css, snapPayloads };
      }

      results = await Promise.all(
        targetNames.map(async (name) => {
          const startTime = performance.now();
          const { css, snapPayloads } = processedSnaps[name];
          const result = await executeTargetWithPrerender({
            name,
            css,
            snapPayloads,
            targets,
            publicFolders,
            viewport: targets[name].viewport,
            cssBlocks,
            apiKey,
            apiSecret,
            endpoint,
            logger,
            isAsync,
          });
          logger.start(`  - ${name}`, { startTime });
          logger.success();
          return { name, result };
        }),
      );
    } else {
      results = await Promise.all(
        targetNames.map(async (name) => {
          const startTime = performance.now();
          const result = await targets[name].execute({
            asyncResults: isAsync,
            targetName: name,
            staticPackage,
            globalCSS: cssBlocks,
            apiKey,
            apiSecret,
            endpoint,<commit_msg>Send pre-rendered payloads to happo.io earlier<commit_after>import fs from 'fs';
import os from 'os';
import path from 'path';
import readline from 'readline';
import { performance } from 'perf_hooks';

import JSDOMDomProvider from './JSDOMDomProvider';
import Logger from './Logger';
import MultipleErrors from './MultipleErrors';
import constructReport from './constructReport';
import createStaticPackage from './createStaticPackage';
import createDynamicEntryPoint from './createDynamicEntryPoint';
import createWebpackBundle from './createWebpackBundle';
import loadCSSFile from './loadCSSFile';
import prepareAssetsPackage from './prepareAssetsPackage';
import processSnapsInBundle from './processSnapsInBundle';

const { VERBOSE = 'false' } = process.env;

function logTargetResults({ name, globalCSS, snapPayloads }) {
  const cssPath = path.join(os.tmpdir(), `happo-verbose-${name}.css`);
  const snippetsPath = path.join(os.tmpdir(), `happo-snippets-${name}.json`);
  fs.writeFileSync(cssPath, JSON.stringify(globalCSS));
  fs.writeFileSync(snippetsPath, JSON.stringify(snapPayloads));
  console.log(`Recorded CSS for target "${name}" can be found in ${cssPath}`);
  console.log(
    `Recorded HTML snippets for target "${name}" can be found in ${snippetsPath}`,
  );
}

function waitForAnyKey() {
  readline.emitKeypressEvents(process.stdin);
  process.stdin.setRawMode(true);
  process.stdin.resume();

  return new Promise((resolve) => {
    process.stdin.once('keypress', (_, key) => {
      if (key.ctrl && key.name === 'c') {
        process.exit();
      } else {
        process.stdin.setRawMode(false);
        process.stdin.pause();
        resolve();
      }
    });
  });
}

function resolveDomProvider({ plugins, jsdomOptions }) {
  const pluginWithProvider = plugins.find(({ DomProvider }) => !!DomProvider);
  if (pluginWithProvider) {
    return pluginWithProvider.DomProvider;
  }
  return JSDOMDomProvider.bind(JSDOMDomProvider, jsdomOptions);
}

async function executeTargetWithPrerender({
  name,
  targets,
  css,
  snapPayloads,
  publicFolders,
  cssBlocks,
  apiKey,
  apiSecret,
  endpoint,
  isAsync,
}) {
  if (!snapPayloads.length) {
    console.warn(`No examples found for target ${name}, skipping`);
    return [];
  }
  const errors = snapPayloads.filter((p) => p.isError);
  if (errors.length === 1) {
    throw errors[0];
  }
  if (errors.length > 1) {
    throw new MultipleErrors(errors);
  }

  const globalCSS = cssBlocks.concat([{ css }]);

  if (VERBOSE === 'true') {
    logTargetResults({ name, globalCSS, snapPayloads });
  }

  const assetsPackage = await prepareAssetsPackage({
    globalCSS,
    snapPayloads,
    publicFolders,
  });
  snapPayloads.forEach((item) => {
    delete item.assetPaths;
  });

  const result = await targets[name].execute({
    asyncResults: isAsync,
    targetName: name,
    assetsPackage,
    globalCSS,
    snapPayloads,
    apiKey,
    apiSecret,
    endpoint,
  });
  return result;
}

async function generateScreenshots(
  {
    apiKey,
    apiSecret,
    stylesheets,
    endpoint,
    targets,
    publicFolders,
    jsdomOptions,
    plugins,
    prerender,
    tmpdir,
    isAsync,
  },
  bundleFile,
  logger,
) {
  const cssBlocks = await Promise.all(
    stylesheets.map(async (sheet) => {
      const { source, id, conditional } =
        typeof sheet === 'string' ? { source: sheet } : sheet;
      const result = {
        source,
        css: await loadCSSFile(source),
      };
      if (id) result.id = id;
      if (conditional) result.conditional = conditional;
      return result;
    }),
  );
  plugins.forEach(({ css }) => {
    if (css) {
      cssBlocks.push({ css });
    }
  });

  const targetNames = Object.keys(targets);
  const tl = targetNames.length;
  const DomProvider = resolveDomProvider({ plugins, jsdomOptions });
  logger.info(`Generating screenshots in ${tl} target${tl > 1 ? 's' : ''}...`);
  try {
    const staticPackage = prerender
      ? undefined
      : await createStaticPackage({
        tmpdir,
        publicFolders,
      });

    let results;
    if (prerender) {
      const prerenderPromises = [];
      for (const name of targetNames) {
        // These tasks are CPU-bound, and we need to be careful about how much
        // memory we are using at one time, so we want to run them serially.
        // eslint-disable-next-line no-await-in-loop
        const { css, snapPayloads } = await processSnapsInBundle(bundleFile, {
          targetName: name,
          publicFolders,
          viewport: targets[name].viewport,
          DomProvider,
        });
        prerenderPromises.push((async () => {
          const startTime = performance.now();
          const result = await executeTargetWithPrerender({
            name,
            css,
            snapPayloads,
            targets,
            publicFolders,
            viewport: targets[name].viewport,
            cssBlocks,
            apiKey,
            apiSecret,
            endpoint,
            logger,
            isAsync,
          });
          logger.start(`  - ${name}`, { startTime });
          logger.success();
          return { name, result };
        })());
      }

      results = await Promise.all(prerenderPromises);
    } else {
      results = await Promise.all(
        targetNames.map(async (name) => {
          const startTime = performance.now();
          const result = await targets[name].execute({
            asyncResults: isAsync,
            targetName: name,
            staticPackage,
            globalCSS: cssBlocks,
            apiKey,
            apiSecret,
            endpoint,
{'fraction_non_alphanumeric': 0.12278067308541649, 'fraction_numerical': 0.0007066513558872891, 'mean_word_length': 2.673588578844906, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '607560', 'n_tokens_mistral': 3347, 'n_tokens_neox': 3286, 'n_words': 810}