hunk.code_window:   estimatedTransferSizes?: Map<string, number>,
   ): void {
     const stats: BundleStats[] = [];
     for (const [file, output] of Object.entries(metafile.outputs)) {
       // Only display JavaScript and CSS files
       if (!file.endsWith('.js') && !file.endsWith('.css')) {
  
hunk.labels: keep keep add keep keep keep
hunk.after_edit:   let unchangedCount = 0;
  
hunk.file_path: packages/angular_devkit/build_angular/src/tools/esbuild/utils.ts
hunk.type: add
file: import * as ansiColors from 'ansi-colors';
  import { spawn, SpawnOptions } from 'child_process';
  import * as child_process from 'child_process';
  import { concat, defer, EMPTY, from, lastValueFrom, catchError, repeat } from 'rxjs';
  import { getGlobalVariable, getGlobalVariablesEnv } from './env';
  import treeKill from 'tree-kill';
  import { delimiter, join, resolve } from 'path';
  
  interface ExecOptions {
    silent?: boolean;
    waitForMatch?: RegExp;
    env?: NodeJS.ProcessEnv;
    stdin?: string;
    cwd?: string;
  }
  
  /**
   * While `NPM_CONFIG_` and `YARN_` are case insensitive we filter based on case.
   * This is because when invoking a command using `yarn` it will add a bunch of these variables in lower case.
   * This causes problems when we try to update the variables during the test setup.
   */
  const NPM_CONFIG_RE = /^(NPM_CONFIG_|YARN_|NO_UPDATE_NOTIFIER)/;
  
  let _processes: child_process.ChildProcess[] = [];
  
  export type ProcessOutput = {
    stdout: string;
    stderr: string;
  };
  
  function _exec(options: ExecOptions, cmd: string, args: string[]): Promise<ProcessOutput> {
    // Create a separate instance to prevent unintended global changes to the color configuration
    const colors = ansiColors.create();
  
    const cwd = options.cwd ?? process.cwd();
    const env = options.env ?? process.env;
  
    console.log(
      `==========================================================================================`,
    );
  
    // Ensure the custom npm and yarn global bin is on the PATH
    // https://docs.npmjs.com/cli/v8/configuring-npm/folders#executables
    const paths = [
      join(getGlobalVariable('yarn-global'), 'bin'),
      join(getGlobalVariable('npm-global'), process.platform.startsWith('win') ? '' : 'bin'),
      env.PATH || process.env['PATH'],
    ].join(delimiter);
  
    args = args.filter((x) => x !== undefined);
    const flags = [
      options.silent && 'silent',
      options.waitForMatch && `matching(${options.waitForMatch})`,
    ]
      .filter((x) => !!x) // Remove false and undefined.
      .join(', ')
      .replace(/^(.+)$/, ' [$1]'); // Proper formatting.
  
    console.log(colors.blue(`Running \`${cmd} ${args.map((x) => `"${x}"`).join(' ')}\`${flags}...`));
    console.log(colors.blue(`CWD: ${cwd}`));
  
    const spawnOptions: SpawnOptions = {
      cwd,
      env: { ...env, PATH: paths },
    };
  
    if (process.platform.startsWith('win')) {
      args.unshift('/c', cmd);
      cmd = 'cmd.exe';
      spawnOptions['stdio'] = 'pipe';
    }
  
    const childProcess = child_process.spawn(cmd, args, spawnOptions);
  
    _processes.push(childProcess);
  
    // Create the error here so the stack shows who called this function.
    const error = new Error();
  
    return new Promise<ProcessOutput>((resolve, reject) => {
      let stdout = '';
      let stderr = '';
      let matched = false;
  
      // Return log info about the current process status
      function envDump() {
        return `STDOUT:\n${stdout}\n\nSTDERR:\n${stderr}`;
      }
  
      childProcess.stdout!.on('data', (data: Buffer) => {
        stdout += data.toString('utf-8');
  
        if (options.waitForMatch && stdout.match(options.waitForMatch)) {
          resolve({ stdout, stderr });
          matched = true;
        }
  
        if (options.silent) {
          return;
        }
  
        data
          .toString('utf-8')
          .split(/[\n\r]+/)
          .filter((line) => line !== '')
          .forEach((line) => console.log('  ' + line));
      });
  
      childProcess.stderr!.on('data', (data: Buffer) => {
        stderr += data.toString('utf-8');
  
        if (options.waitForMatch && stderr.match(options.waitForMatch)) {
          resolve({ stdout, stderr });
          matched = true;
        }
  
        if (options.silent) {
          return;
        }
  
        data
          .toString('utf-8')
          .split(/[\n\r]+/)
          .filter((line) => line !== '')
          .forEach((line) => console.error(colors.yellow('  ' + line)));
      });
  
      childProcess.on('close', (code) => {
        _processes = _processes.filter((p) => p !== childProcess);
  
        if (options.waitForMatch && !matched) {
          reject(
            `Process output didn't match - "${cmd} ${args.join(' ')}": '${
              options.waitForMatch
            }': ${code}...\n\n${envDump()}\n`,
          );
          return;
        }
  
        if (!code) {
          resolve({ stdout, stderr });
          return;
        }
  
        reject(`Process exit error - "${cmd} ${args.join(' ')}": ${code}...\n\n${envDump()}\n`);
      });
  
      childProcess.on('error', (err) => {
        reject(`Process error - "${cmd} ${args.join(' ')}": ${err}...\n\n${envDump()}\n`);
      });
  
      // Provide input to stdin if given.
      if (options.stdin) {
        childProcess.stdin!.write(options.stdin);
        childProcess.stdin!.end();
      }
    }).catch((err) => {
      error.message = err.toString();
      return Promise.reject(error);
    });
  }
  
  export function extractNpmEnv() {
    return Object.keys(process.env)
      .filter((v) => NPM_CONFIG_RE.test(v))
      .reduce<NodeJS.ProcessEnv>((vars, n) => {
        vars[n] = process.env[n];
        return vars;
      }, {});
  }
  
  function extractCIEnv(): NodeJS.ProcessEnv {
    return Object.keys(process.env)
      .filter(
        (v) =>
          v.startsWith('SAUCE_') ||
          v === 'CI' ||
          v === 'CIRCLECI' ||
          v === 'CHROME_BIN' ||
          v === 'CHROMEDRIVER_BIN',
      )
      .reduce<NodeJS.ProcessEnv>((vars, n) => {
        vars[n] = process.env[n];
        return vars;
      }, {});
  }
  
  function extractNgEnv() {
    return Object.keys(process.env)
      .filter((v) => v.startsWith('NG_'))
      .reduce<NodeJS.ProcessEnv>((vars, n) => {
        vars[n] = process.env[n];
        return vars;
      }, {});
  }
  
  export function waitForAnyProcessOutputToMatch(
    match: RegExp,
    timeout = 30000,
  ): Promise<ProcessOutput> {
    // Race between _all_ processes, and the timeout. First one to resolve/reject wins.
    const timeoutPromise: Promise<ProcessOutput> = new Promise((_resolve, reject) => {
      // Wait for 30 seconds and timeout.
      setTimeout(() => {
        reject(new Error(`Waiting for ${match} timed out (timeout: ${timeout}msec)...`));
      }, timeout);
    });
  
    const matchPromises: Promise<ProcessOutput>[] = _processes.map(
      (childProcess) =>
        new Promise((resolve) => {
          let stdout = '';
          let stderr = '';
  
          childProcess.stdout!.on('data', (data: Buffer) => {
            stdout += data.toString();
            if (stdout.match(match)) {
              resolve({ stdout, stderr });
            }
          });
  
          childProcess.stderr!.on('data', (data: Buffer) => {
            stderr += data.toString();
            if (stderr.match(match)) {
              resolve({ stdout, stderr });
            }
          });
        }),
    );
  
    return Promise.race(matchPromises.concat([timeoutPromise]));
  }
  
  export async function killAllProcesses(signal = 'SIGTERM'): Promise<void> {
    const processesToKill: Promise<void>[] = [];
  
    while (_processes.length) {
      const childProc = _processes.pop();
      if (!childProc || childProc.pid === undefined) {
        continue;
      }
  
      processesToKill.push(
        new Promise<void>((resolve) => {
          treeKill(childProc.pid!, signal, () => {
            // Ignore all errors.
            // This is due to a race condition with the `waitForMatch` logic.
            // where promises are resolved on matches and not when the process terminates.
            // Also in some cases in windows we get `The operation attempted is not supported`.
            resolve();
          });
        }),
      );
    }
  
    await Promise.all(processesToKill);
  }
  
  export function exec(cmd: string, ...args: string[]) {
    return _exec({}, cmd, args);
  }
  
  export function silentExec(cmd: string, ...args: string[]) {
    return _exec({ silent: true }, cmd, args);
  }
  
  export function execWithEnv(cmd: string, args: string[], env: NodeJS.ProcessEnv, stdin?: string) {
    return _exec({ env, stdin }, cmd, args);
  }
  
  export async function execAndCaptureError(
    cmd: string,
    args: string[],
    env?: NodeJS.ProcessEnv,
    stdin?: string,
  ): Promise<Error> {
    try {
      await _exec({ env, stdin }, cmd, args);
      throw new Error('Tried to capture subprocess exception, but it completed successfully.');
    } catch (err) {
      if (err instanceof Error) {
        return err;
      }
      throw new Error('Subprocess exception was not an Error instance');
    }
  }
  
  export function execAndWaitForOutputToMatch(
    cmd: string,
    args: string[],
    match: RegExp,
    env?: NodeJS.ProcessEnv,
  ) {
    if (cmd === 'ng' && args[0] === 'serve') {
      // Accept matches up to 20 times after the initial match.
      // Useful because the Webpack watcher can rebuild a few times due to files changes that
      // happened just before the build (e.g. `git clean`).
      // This seems to be due to host file system differences, see
      // https://nodejs.org/docs/latest/api/fs.html#fs_caveats
      return lastValueFrom(
        concat(
          from(_exec({ waitForMatch: match, env }, cmd, args)),
          defer(() => waitForAnyProcessOutputToMatch(match, 2500)).pipe(
            repeat(20),
            catchError(() => EMPTY),
          ),
        ),
      );
    } else {
      return _exec({ waitForMatch: match, env }, cmd, args);
    }
  }
  
  export function ng(...args: string[]) {
    const argv = getGlobalVariable('argv');
    const maybeSilentNg = argv['nosilent'] ? noSilentNg : silentNg;
    if (['build', 'serve', 'test', 'e2e', 'extract-i18n'].indexOf(args[0]) != -1) {
      if (args[0] == 'e2e') {
        // Wait 1 second before running any end-to-end test.
        return new Promise((resolve) => setTimeout(resolve, 1000)).then(() => maybeSilentNg(...args));
      }
  
      return maybeSilentNg(...args);
    } else {
      return noSilentNg(...args);
    }
  }
  
  export function noSilentNg(...args: string[]) {
    return _exec({}, 'ng', args);
  }
  
  export function silentNg(...args: string[]) {
    return _exec({ silent: true }, 'ng', args);
  }
  
  export function silentNpm(...args: string[]): Promise<ProcessOutput>;
  export function silentNpm(args: string[], options?: { cwd?: string }): Promise<ProcessOutput>;
  export function silentNpm(
    ...args: string[] | [args: string[], options?: { cwd?: string }]
  ): Promise<ProcessOutput> {
    if (Array.isArray(args[0])) {
      const [params, options] = args;
      return _exec(
        {
          silent: true,
          cwd: (options as { cwd?: string } | undefined)?.cwd,
        },
        'npm',
        params,
      );
    } else {
      return _exec({ silent: true }, 'npm', args as string[]);
    }
  }
  
  export function silentYarn(...args: string[]) {
    return _exec({ silent: true }, 'yarn', args);
  }
  
  export function globalNpm(args: string[], env?: NodeJS.ProcessEnv) {
    if (!process.env.LEGACY_CLI_RUNNER) {
      throw new Error(
        'The global npm cli should only be executed from the primary e2e runner process',
      );
    }
  
    return _exec({ silent: true, env }, process.execPath, [require.resolve('npm'), ...args]);
  }
  
  export function npm(...args: string[]) {
    return _exec({}, 'npm', args);
  }
  
  export function node(...args: string[]) {
    return _exec({}, process.execPath, args);
  }
  
  export function git(...args: string[]) {
    return _exec({}, process.env.GIT_BIN || 'git', args);
  }
  
  export function silentGit(...args: string[]) {
    return _exec({ silent: true }, process.env.GIT_BIN || 'git', args);
  }
  
  /**
   * Launch the given entry in an child process isolated to the test environment.
   *
   * The test environment includes the local NPM registry, isolated NPM globals,
   * the PATH variable only referencing the local node_modules and local NPM
   * registry (not the test runner or standard global node_modules).
   */
  export async function launchTestProcess(entry: string, ...args: any[]): Promise<void> {
    // NOTE: do NOT use the bazel TEST_TMPDIR. When sandboxing is not enabled the
    // TEST_TMPDIR is not sandboxed and has symlinks into the src dir in a
    // parent directory. Symlinks into the src dir will include package.json,
    // .git and other files/folders that may effect e2e tests.
  
    const tempRoot: string = getGlobalVariable('tmp-root');
    const TEMP = process.env.TEMP ?? process.env.TMPDIR ?? tempRoot;
  
    // Extract explicit environment variables for the test process.
    const env: NodeJS.ProcessEnv = {
      TEMP,
      TMPDIR: TEMP,
      HOME: TEMP,
  
      // Use BAZEL_TARGET as a metadata variable to show it is a
      // process managed by bazel
      BAZEL_TARGET: process.env.BAZEL_TARGET,
  
      ...extractNpmEnv(),
      ...extractCIEnv(),
      ...extractNgEnv(),
      ...getGlobalVariablesEnv(),
    };
  
    // Only include paths within the sandboxed test environment or external
    // non angular-cli paths such as /usr/bin for generic commands.
    env.PATH = process.env
      .PATH!.split(delimiter)
      .filter((p) => p.startsWith(tempRoot) || p.startsWith(TEMP) || !p.includes('angular-cli'))
      .join(delimiter);
  
    const testProcessArgs = [resolve(__dirname, 'test_process'), entry, ...args];
  
    return new Promise<void>((resolve, reject) => {
      spawn(process.execPath, testProcessArgs, {
        stdio: 'inherit',
        env,
      })
        .on('close', (code) => {
          if (!code) {
            resolve();
            return;
          }
  
          reject(`Process error - "${testProcessArgs}`);
        })
        .on('error', (err) => {
          reject(`Process exit error - "${testProcessArgs}]\n\n${err}`);
        });
    });
  }
  