repo_description: 🐠 Babel is a compiler for writing next generation JavaScript.
repo_topics: ast babel compiler es2015 es6 flavortown javascript
code: import path from "node:path";
  import fs from "node:fs";
  import { cpus } from "node:os";
  import { createRequire } from "node:module";
  import { fileURLToPath } from "node:url";
  import { Transform as TransformStream } from "node:stream";
  import { callbackify } from "node:util";
  import { styleText } from "node:util";
  // @ts-expect-error no types
  import gulp from "gulp";
  import { rollup, type InputPluginOption } from "rollup";
  import {
    babel as rollupBabel,
    getBabelOutputPlugin,
  } from "@rollup/plugin-babel";
  import rollupCommonJs from "@rollup/plugin-commonjs";
  import rollupJson from "@rollup/plugin-json";
  import rollupPolyfillNode from "rollup-plugin-polyfill-node";
  import rollupNodeResolve from "@rollup/plugin-node-resolve";
  import rollupReplace from "@rollup/plugin-replace";
  import rollupTerser from "@rollup/plugin-terser";
  import rollupDts from "rollup-plugin-dts";
  import { Worker as JestWorker } from "jest-worker";
  import { Glob } from "glob";
  import { patchErrorInfo } from "./packages/babel-parser/scripts/build-error-info.ts";
  
  import rollupBabelSource from "./scripts/rollup-plugin-babel-source.ts";
  import rollupStandaloneInternals from "./scripts/rollup-plugin-standalone-internals.ts";
  import rollupDependencyCondition from "./scripts/rollup-plugin-dependency-condition.ts";
  import babelPluginToggleBooleanFlag from "./scripts/babel-plugin-toggle-boolean-flag/plugin.ts";
  import formatCode from "./scripts/utils/formatCode.ts";
  import { log } from "./scripts/utils/logger.ts";
  import { commonJS } from "$repo-utils";
  
  import type { NodePath, PluginItem, types } from "@babel/core";
  
  const { require, __dirname: monorepoRoot } = commonJS(import.meta.url);
  
  const defaultPackagesGlob = "./@(codemods|packages|eslint)/*";
  const defaultSourcesGlob = [
    `${defaultPackagesGlob}/src/**/{*.js,*.cjs,!(*.d).ts,!(*.d).cts,!(*.d).mts}`,
    "!./packages/babel-helpers/src/helpers/*",
  ];
  
  const babelStandalonePluginConfigGlob =
    "./packages/babel-standalone/scripts/pluginConfig.json";
  
  const buildTypingsWatchGlob = [
    "./packages/babel-types/lib/definitions/**/*.js",
    "./packages/babel-types/scripts/generators/*.ts",
  ];
  
  // env vars from the cli are always strings, so !!ENV_VAR returns true for "false"
  function bool(value: string | undefined): boolean {
    return Boolean(value) && value !== "false" && value !== "0";
  }
  
  /**
   * map source code path to the generated artifacts path
   * @example
   * mapSrcToLib("packages/babel-core/src/index.js")
   * // returns "packages/babel-core/lib/index.js"
   * @example
   * mapSrcToLib("packages/babel-template/src/index.ts")
   * // returns "packages/babel-template/lib/index.js"
   * @example
   * mapSrcToLib("packages/babel-template/src/index.d.ts")
   * // returns "packages/babel-template/lib/index.d.ts"
   */
  function mapSrcToLib(srcPath: string): string {
    const parts = srcPath
      .replace(/(?<!\.d)\.ts$/, ".js")
      .replace(/(?<!\.d)\.cts$/, ".cjs")
      .replace(/(?<!\.d)\.mts$/, ".mjs")
      .split("/");
    parts[2] = "lib";
    return parts.join("/");
  }
  
  function mapToDts(packageName: string): string {
    return packageName.replace(
      /(?<=\\|\/|^)(packages|eslint|codemods)(?=\\|\/)/,
      "dts/$1"
    );
  }
  
  function getIndexFromPackage(name: string): string {
    try {
      fs.statSync(`./${name}/src/index.ts`);
      return `${name}/src/index.ts`;
    } catch {
      return `${name}/src/index.js`;
    }
  }
  
  async function generateHelpers(
    generator: string,
    dest: string,
    filename: string,
    message: string
  ) {
    const { default: generateCode } = await import(generator);
    const result = await formatCode(
      await generateCode(filename),
      path.join(dest, filename)
    );
    fs.writeFileSync(path.join(dest, filename), result, { mode: 0o644 });
    log(`${styleText("green", "✔")} Generated ${message}`);
  }
  
  type TypesHelperKind =
    | "asserts"
    | "ast-types"
    | "builders"
    | "constants"
    | "validators";
  
  function generateTypeHelpers(
    helperKind: TypesHelperKind,
    filename = "index.ts"
  ) {
    return generateHelpers(
      `./packages/babel-types/scripts/generators/${helperKind}.ts`,
      `./packages/babel-types/src/${helperKind}/generated/`,
      filename,
      `@babel/types -> ${helperKind}`
    );
  }
  
  type TraverseHelperKind = "asserts" | "validators" | "visitor-types";
  
  function generateTraverseHelpers(helperKind: TraverseHelperKind, outBase = "") {
    return generateHelpers(
      `./packages/babel-traverse/scripts/generators/${helperKind}.ts`,
      `./packages/babel-traverse/src/${outBase}/generated/`,
      `${helperKind}.d.ts`,
      `@babel/traverse -> ${helperKind}`
    );
  }
  
  function generateHelperGlobalsData(filename: string) {
    return generateHelpers(
      `./packages/babel-helper-globals/scripts/generate.mjs`,
      `./packages/babel-helper-globals/data/`,
      filename,
      `@babel/helper-globals -> ${filename}`
    );
  }
  
  async function applyBabelToSource(
    inputCode: string,
    filename: string,
    options: any
  ) {
    const { transformAsync } = await import("@babel/core");
    return transformAsync(inputCode, {
      configFile: false,
      filename,
      ...options,
      parserOpts: {
        tokens: true,
        ...options?.parserOpts,
      },
      generatorOpts: {
        retainLines: true,
        experimental_preserveFormat: true,
        ...options?.generatorOpts,
      },
    }).then(res => formatCode(res!.code!, filename));
  }
  
  function applyBabelToGlob(glob: any, options: any) {
    const promises = [];
  
    const ac = new AbortController();
  
    for (const file of glob) {
      promises.push(
        fs.promises
          .readFile(file, "utf-8")
          .then(code => {
            ac.signal.throwIfAborted();
            return applyBabelToSource(code, file, options);
          })
          .then(transformedCode => {
            ac.signal.throwIfAborted();
            return fs.promises.writeFile(file, transformedCode);
          })
      );
    }
  
    return Promise.all(promises).catch(err => {
      ac.abort();
      throw err;
    });
  }
  
  const kebabToCamel = (str: string) =>
    str.replace(/-[a-z]/g, c => c[1].toUpperCase());
  
  function generateStandalone() {
    const dest = "./packages/babel-standalone/src/generated/";
    return gulp
      .src(babelStandalonePluginConfigGlob, { base: monorepoRoot })
      .pipe(
        new TransformStream({
          objectMode: true,
          // @ts-expect-error FIXME
          transform: callbackify(async file => {
            log("Generating @babel/standalone files");
            const pluginConfig = JSON.parse(file.contents);
            let imports = `import makeNoopPlugin from "../make-noop-plugin.ts";`;
            let exportDecls = "";
            let exportsList = "";
            let allList = "";
  
            for (const plugin of pluginConfig.noopPlugins) {
              const camelPlugin = kebabToCamel(plugin);
              exportDecls += `${camelPlugin} = makeNoopPlugin(),`;
              allList += `"${plugin}": ${camelPlugin},`;
            }
  
            for (const plugin of pluginConfig.externalPlugins) {
              const camelPlugin = kebabToCamel(plugin);
              imports += `import ${camelPlugin} from "@babel/plugin-${plugin}";`;
              exportsList += `${camelPlugin},`;
              allList += `"${plugin}": ${camelPlugin},`;
            }
  
            const fileContents = `/*
     * This file is auto-generated! Do not modify it directly.
     * To re-generate run 'yarn gulp generate-standalone'
     */
    ${imports}
    export const ${exportDecls.slice(0, -1)};
    export {${exportsList}};
    export const all: Record<string, any> = {${allList}};`;
            file.path = "plugins.ts";
            file.contents = Buffer.from(
              await formatCode(fileContents, dest + file.path)
            );
  
            return file;
          }),
        })
      )
      .pipe(gulp.dest(dest));
  }
  
  function createWorker(useWorker: boolean): any {
    const numWorkers = Math.ceil(Math.max(cpus().length, 1) / 2) - 1;
    if (
      numWorkers === 0 ||
      !useWorker ||
      // For some reason, on CircleCI the workers hang indefinitely.
      process.env.CIRCLECI
    ) {
      return import("./babel-worker.ts");
    }
    const worker = new JestWorker(new URL("./babel-worker.ts", import.meta.url), {
      enableWorkerThreads: true,
      numWorkers,
      exposedMethods: ["transform"],
    });
  
    worker.getStdout().pipe(process.stdout);
    worker.getStderr().pipe(process.stderr);
    return worker;
  }
  
  async function buildBabel(useWorker: boolean, ignore: PackageInfo[] = []) {
    const worker = await createWorker(useWorker);
    const files = new Glob(defaultSourcesGlob, {
      ignore: ignore.map(p => `${p.src}/**`),
      posix: true,
    });
  
    const promises = [];
    for await (const file of files) {
      // @example ./packages/babel-parser/src/index.js
      const dest = "./" + mapSrcToLib(file);
      promises.push(
        worker.transform(file, dest, {
          sourceMaps: !file.endsWith(".d.ts"),
        })
      );
    }
    return Promise.allSettled(promises)
      .then(results => {
        results.forEach(result => {
          if (result.status === "rejected") {
            if (process.env.WATCH_SKIP_BUILD) {
              console.error(result.reason);
            } else {
              throw result.reason;
            }
          }
        });
      })
      .finally(() => {
        worker.end?.();
      });
  }
  
  /**
   * Resolve a nested dependency starting from the given file
   */
  function resolveChain(baseUrl: string, ...packages: string[]) {
    const require = createRequire(baseUrl);
  
    return packages.reduce(
      (base, pkg) =>
        path.dirname(require.resolve(pkg + "/package.json", { paths: [base] })),
      path.dirname(fileURLToPath(baseUrl))
    );
  }
  
  // If this build is part of a pull request, include the pull request number in
  // the version number.
  let versionSuffix = "";
  if (process.env.CIRCLE_PR_NUMBER) {
    versionSuffix = "+pr." + process.env.CIRCLE_PR_NUMBER;
  }
  
  const babelVersion =
    require("./packages/babel-core/package.json").version + versionSuffix;
  function buildRollup(packages: PackageInfo[], buildStandalone?: boolean) {
    const sourcemap = process.env.NODE_ENV === "production";
    return Promise.all(
      packages.map(
        async ({
          src,
          format,
          inputs,
          dest,
          name,
          filename,
          envName = "rollup",
          pkgJSON,
        }) => {
          const version = pkgJSON.version + versionSuffix;
          const {
            dependencies = {},
            peerDependencies = {},
            imports = {},
          } = pkgJSON;
          const external = [
            ...Object.keys(dependencies),
            ...Object.keys(peerDependencies),
            ...Object.keys(imports),
            // @babel/compat-data sub exports
            /@babel\/compat-data\/.*/,
            // @babel/helper-globals sub exports
            /@babel\/helper-globals\/.*/,
            // Ideally they should be constructed from package.json exports
            // required by modules-commonjs
            /babel-plugin-dynamic-import-node\/utils/,
            // required by preset-env
            /@babel\/preset-modules\/.*/,
            // required by babel-node
            "core-js/stable/index.js",
            "kexec",
            // required by eslint-plugin
            "eslint/use-at-your-own-risk",
          ];
  
          log(
            `Compiling '${styleText("cyan", inputs.join(", "))}' with rollup ...`
          );
          const bundle = await rollup({
            input: Object.fromEntries(
              inputs.map(inputName => [
                filename ||
                  // ./packages/babel-foo/src/bar/baz.ts -> bar/baz.js
                  inputName
                    .split("/")
                    .slice(4)
                    .join("/")
                    .replace(/(\.[cm]?)ts$/, "$1js"),
                inputName,
              ])
            ),
            external: buildStandalone ? [] : external,
            // all node modules are resolved as if they were placed in the n_m folder of package root
            preserveSymlinks: true,
            onwarn(warning, warn) {
              switch (warning.code) {
                case "CIRCULAR_DEPENDENCY":
                case "SOURCEMAP_ERROR": // Rollup warns about the babel-polyfills source maps
                case "INCONSISTENT_IMPORT_ATTRIBUTES": // @rollup/plugin-commonjs transforms require("...json") to an import without attributes
                  return;
                case "UNUSED_EXTERNAL_IMPORT":
                  warn(warning);
                  return;
                case "MISSING_EXPORT": {
                  const exporter = warning.exporter!.replace(/\\/g, "/");
                  const specifier = warning.id!.replace(/\\/g, "/");
                  // Rollup warns about using babel.default, for example at
                  // https://npmx.dev/package-code/babel-plugin-polyfill-corejs3/v/1.0.0-rc.2/lib%2Findex.js#L489
                  // https://npmx.dev/package-code/@babel/helper-define-polyfill-provider/v/1.0.0-rc.2/lib%2Findex.browser.js#L792
                  // We can safely ignore this warning, and let Rollup replace it with undefined.
                  if (
                    (exporter.endsWith("packages/babel-core/src/index.ts") ||
                      exporter.endsWith(
                        "packages/babel-standalone/src/plugin-utils-shim.ts"
                      )) &&
                    warning.binding === "default" &&
                    [
                      "@babel/helper-define-polyfill-provider",
                      "babel-plugin-polyfill-corejs3",
                      "babel-plugin-polyfill-regenerator",
                    ].some(pkg => specifier.includes(pkg))
                  ) {
                    return;
                  }
                  break;
                }
                case "EMPTY_BUNDLE":
                  if (
                    warning.names?.length === 1 &&
                    warning.names[0] === "babel-helpers-in-memory.js"
                  ) {
                    // babel-helpers-in-memory.js is an empty file for jest v8 coverage provider
                    return;
                  }
              }
  
              // We use console.warn here since it prints more info than just "warn",
              // in case we want to stop throwing for a specific message.
              console.warn(warning);
  
              // https://github.com/babel/babel/pull/12011#discussion_r540434534
              throw new Error("Rollup aborted due to warnings above");
            },
            plugins: [
              buildStandalone && rollupStandaloneInternals(),
              rollupBabelSource(),
              process.env.STRIP_BABEL_VERSION_FLAG &&
                rollupDependencyCondition(bool(process.env.BABEL_9_BREAKING)),
              rollupReplace({
                preventAssignment: true,
                values: {
                  ...(buildStandalone
                    ? {
                        "process.env.NODE_ENV": JSON.stringify(
                          process.env.NODE_ENV
                        ),
                      }
                    : {}),
                  BABEL_VERSION: JSON.stringify(babelVersion),
                  VERSION: JSON.stringify(version),
                },
              }),
              rollupCommonJs({
                include: [
                  // Bundle node_modules only when building standalone
                  buildStandalone ? /node_modules/ : "./node_modules/*/*.js",
                  "packages/babel-runtime/regenerator/**",
                  "packages/babel-runtime/helpers/*.js",
                  "packages/babel-preset-env/data/*.js",
                  // Rollup doesn't read export maps, so it loads the cjs fallback
                  "packages/babel-compat-data/*.js",
                  // Used by @babel/standalone
                  "packages/babel-compat-data/scripts/data/legacy-plugin-aliases.js",
                  "packages/*/src/**/*.cjs",
                ],
                dynamicRequireTargets: [
                  // https://github.com/mathiasbynens/regexpu-core/blob/ffd8fff2e31f4597f6fdfee75d5ac1c5c8111ec3/rewrite-pattern.js#L48
                  resolveChain(
                    import.meta.url,
                    "./packages/babel-helper-create-regexp-features-plugin",
                    "regexpu-core",
                    "regenerate-unicode-properties"
                  ).replace(/\\/g, "/") + "/**/*.js", // Must be posix path in rollup 3
                ],
                // Never delegate to the native require()
                ignoreDynamicRequires: false,
                // Align with the Node.js behavior
                defaultIsModuleExports: true,
              }),
              rollupBabel({
                envName,
                babelHelpers: "bundled",
                configFile: "./babel.config.ts",
                extensions: [".ts", ".js", ".mjs", ".cjs"],
                ignore: ["packages/babel-runtime/helpers/*.js"],
              }),
              buildStandalone && {
                resolveId: {
                  order: "post",
                  // This is needed because @jridgewell's packages always use
                  // the UMD version when targeting browsers, but we need to use
                  // the ESM version so that it can be bundled.
                  handler(importee: string) {
                    if (/@jridgewell[\\/].*\.umd\.js$/.test(importee)) {
                      return importee.slice(0, -".umd.js".length) + ".mjs";
                    }
                  },
                },
              },
              rollupNodeResolve({
                extensions: [".ts", ".js", ".mjs", ".cjs", ".json"],
                browser: buildStandalone,
                // Used in some package.json#imports
                exportConditions: buildStandalone
                  ? ["babel-src", "browser"]
                  : ["babel-src"],
                // It needs to be set to 'false' when using rollupNodePolyfills
                // https://github.com/rollup/plugins/issues/772
                preferBuiltins: !buildStandalone,
              }),
              rollupJson(),
              ["packages/babel-parser", "packages/babel-generator"].includes(
                src
              ) &&
                getBabelOutputPlugin({
                  configFile: false,
                  babelrc: false,
                  generatorOpts: {
                    compact: false,
                  },
                  plugins: [
                    function babelPluginInlineConstNumericObjects({ types: t }) {
                      return {
                        visitor: {
                          VariableDeclarator(path) {
                            const { node } = path;
                            if (
                              !t.isIdentifier(node.id) ||
                              !t.isObjectExpression(node.init)
                            ) {
                              return;
                            }
  
                            const binding = path.scope.getBinding(node.id.name)!;
                            if (!binding.constant) return;
  
                            const vals = new Map();
                            for (const { key, value } of node.init
                              .properties as types.ObjectProperty[]) {
                              if (!t.isIdentifier(key)) return;
                              if (!t.isNumericLiteral(value)) return;
                              vals.set(key.name, value.value);
                            }
  
                            let all = true;
                            binding.referencePaths.forEach(({ parentPath }) => {
                              const { node } = parentPath;
                              if (
                                !t.isMemberExpression(node) ||
                                !t.isIdentifier(node.property) ||
                                node.computed ||
                                !vals.has(node.property.name)
                              ) {
                                all = false;
                              }
                            });
  
                            if (all) {
                              binding.referencePaths.forEach(({ parentPath }) => {
                                const { node } = parentPath;
                                parentPath.replaceWith(
                                  // @ts-expect-error checked above
                                  t.numericLiteral(vals.get(node.property.name))
                                );
                              });
                              path.remove();
                            }
                          },
                        },
                      };
                    } satisfies PluginItem,
                  ],
                }),
              buildStandalone &&
                rollupPolyfillNode({
                  sourceMap: sourcemap,
                  include: "**/*.{js,mjs,cjs,ts}",
                }),
              // https://github.com/babel/babel/issues/14301
              buildStandalone &&
                rollupReplace({
                  preventAssignment: false,
                  delimiters: ["", ""],
                  values: {
                    "return require.resolve(path);":
                      "throw new Error('Babel internal error');",
                  },
                }),
            ].filter(Boolean) as InputPluginOption[],
          });
  
          await bundle.write({
            esModule: true,
            hoistTransitiveImports: false,
            minifyInternalExports: false,
            freeze: false,
            generatedCode: buildStandalone ? "es5" : "es2015",
            dir: path.join(src, dest),
            entryFileNames: "[name]",
            chunkFileNames: "[name]-shared.js",
            format,
            importAttributesKey: "with",
            name,
            sourcemap: sourcemap,
            exports: "named",
            interop(id) {
              if (!id) return "default";
              // We have manually applied commonjs-esm interop to the source
              // for library not in this monorepo
              // https://github.com/babel/babel/pull/12795
              if (!id.startsWith("@babel/")) return "default";
  
              // Some syntax plugins have been archived
              if (id.includes("plugin-syntax")) {
                const srcPath = path.join(
                  path.dirname(fileURLToPath(import.meta.url)),
                  "/packages/" + id.replace("@babel/", "babel-")
                );
                if (!fs.existsSync(srcPath)) return "default";
              }
  
              if (id.includes("@babel/preset-modules")) {
                return "default";
              }
  
              return "auto";
            },
          });
  
          // Only minify @babel/standalone
          if (!buildStandalone) {
            return;
          }
  
          if (!filename) {
            throw new Error("filename is required for babel-standalone");
          }
  
          const outputFile = path.join(src, dest, filename);
  
          if (!process.env.IS_PUBLISH) {
            log(
              styleText(
                "yellow",
                `Skipped minification of '${styleText(
                  "cyan",
                  outputFile
                )}' because not publishing`
              )
            );
            return undefined;
          }
          log(`Minifying '${styleText("cyan", outputFile)}'...`);
  
          await bundle.write({
            entryFileNames: filename.replace(/\.js$/, ".min.js"),
            dir: path.join(src, dest),
            format,
            esModule: true,
            interop: "compat",
            name,
            sourcemap: sourcemap,
            exports: "named",
            plugins: [
              rollupTerser({
                // workaround https://bugs.webkit.org/show_bug.cgi?id=212725
                format: {
                  ascii_only: true,
                },
                maxWorkers: process.env.CIRCLECI ? 1 : undefined,
              }),
            ],
          });
        }
      )
    );
  }
  
  async function buildRollupDts(
    input: string,
    output: string,
    banner: string,
    packageName: string
  ) {
    log(`Bundling '${styleText("cyan", output)}' with rollup ...`);
  
    let external;
    let imports;
    if (packageName) {
      const pkgJSON = require("./" + packageName + "/package.json");
      const {
        dependencies = {},
        devDependencies = {},
        peerDependencies = {},
      } = pkgJSON;
      external = [
        ...Object.keys(dependencies),
        ...Object.keys(peerDependencies),
        // TODO: These should all be moved to dependencies
        ...Object.keys(devDependencies),
      ].map(dep => new RegExp(`^${dep}(?:/.+)?$`));
  
      imports = pkgJSON.imports;
    }
  
    const bundle = await rollup({
      input,
      plugins: (
        [
          {
            name: "rollup-babel-internal-define-BABEL_8_BREAKING",
            transform: code =>
              code.replace(
                /type BABEL_8_BREAKING\s*=\s*boolean/g,
                `type BABEL_8_BREAKING = true`
              ),
          },
          rollupDts(),
          imports && {
            name: "resolve-dts-package.json-imports",
            resolveId: {
              order: "post",
              handler(importee: string, importer: string | undefined) {
                if (importer && importee.startsWith("#")) {
                  const mapped = imports?.[importee]?.types?.replace(
                    "/lib/",
                    "/src/"
                  );
                  if (mapped) {
                    return require.resolve(`./dts/${packageName}/${mapped}`);
                  }
                  return null;
                }
              },
            },
          },
        ] satisfies InputPluginOption
      ).filter(Boolean),
      external,
      onwarn(warning) {
        if (
          warning.code === "UNUSED_EXTERNAL_IMPORT" &&
          // @ts-expect-error warning.names are defined when code is UNUSED_EXTERNAL_IMPORT
          warning.names.length === 1 &&
          // @ts-expect-error warning.names are defined when code is UNUSED_EXTERNAL_IMPORT
          warning.names[0] === "default"
        ) {
          // rollup-plugin-dts doesn't like default imports when they are just re-exported
          return;
        }
        if (warning.code === "UNRESOLVED_IMPORT" && warning.exporter === "vm") {
          // TODO: We probably need @types/node
          return;
        }
        console.warn(warning);
      },
    });
  
    await bundle.write({
      file: output,
      format: "es",
      banner,
    });
  }
  
  async function buildPackagesDts(packages: string[]) {
    await Promise.all(
      packages.map(async packageName => {
        const input = `${mapToDts(packageName)}/src/index.d.ts`;
        const output = `${packageName}/lib/index.d.ts`;
        await buildRollupDts(input, output, "", packageName);
      })
    );
  }
  
  async function buildBabelParserDts() {
    const parserDts = "packages/babel-parser/typings/babel-parser.d.ts";
    await buildRollupDts(
      "packages/babel-parser/typings/babel-parser.source.d.ts",
      parserDts,
      "// This file is auto-generated! Do not modify it directly.\n" +
        "// Run `make bundle-babel-parser-dts` to re-generate it.\n" +
        // @typescript-eslint/no-redundant-type-constituents can be removed once we drop the IF_BABEL_7 type
        "/* eslint-disable @typescript-eslint/consistent-type-imports, @typescript-eslint/no-redundant-type-constituents */",
      "packages/babel-parser"
    );
  
    patchErrorInfo(parserDts);
    await buildRollupDts(parserDts, parserDts, "", "packages/babel-parser");
    fs.writeFileSync(
      parserDts,
      await formatCode(fs.readFileSync(parserDts, "utf8"), parserDts)
    );
  }
  
  function* packagesIterator(exclude: Set<string>) {
    for (const packageDir of ["packages", "codemods", "eslint"]) {
      for (const dir of fs.readdirSync(new URL(packageDir, import.meta.url))) {
        const src = `${packageDir}/${dir}`;
        if (exclude.has(dir)) continue;
        if (!fs.existsSync(new URL(`${src}/package.json`, import.meta.url))) {
          continue;
        }
        yield src;
      }
    }
  }
  
  type DeepStringsObject = null | string | { [key: string]: DeepStringsObject };
  
  function* getPackageExports(
    exports: DeepStringsObject,
    conditions: Set<string>,
    pathFilter: RegExp
  ): IterableIterator<string> {
    if (typeof exports === "string") {
      if (pathFilter.test(exports)) yield exports;
    } else if (exports !== null) {
      for (const [key, value] of Object.entries(exports)) {
        if (conditions.has(key) || key.startsWith(".") || key.startsWith("#")) {
          yield* getPackageExports(value, conditions, pathFilter);
        }
      }
    }
  }
  
  function* libBundlesIterator(): IterableIterator<PackageInfo> {
    const extraPackagesEntries = new Map<string, string[]>([
      ["babel-build-external-helpers", ["./lib/babel-build-external-helpers.js"]],
      ["babel-cli", ["./lib/babel/index.js"]],
      [
        "babel-helper-transform-fixture-test-runner",
        ["./lib/babel-helpers-in-memory.js", "./lib/exit-loader.cjs"],
      ],
      ["babel-node", ["./lib/babel-node.js", "./lib/_babel-node.js"]],
      ["babel-register", ["./lib/worker/index.js"]],
      ["babel-eslint-parser", ["./lib/worker/index.js"]],
    ]);
    // Todo: convert these packages to TS
    const jsPackages = new Set([
      "babel-eslint-plugin",
      "babel-eslint-plugin-development",
      "babel-eslint-plugin-development-internal",
    ]);
    const noBundle = new Set([
      // No need to bundle JSON files
      "babel-compat-data",
      "babel-helper-globals",
      // babel-standalone is handled by rollup-babel-standalone task
      "babel-standalone",
      // Many entry points
      "babel-runtime",
      "babel-runtime-corejs3",
    ]);
    for (const src of packagesIterator(noBundle)) {
      const pkgJSON = JSON.parse(
        fs.readFileSync(new URL(`${src}/package.json`, import.meta.url), "utf-8")
      );
      if (pkgJSON.private) {
        // Skip private packages since they are not meant to be consumed by users
        continue;
      }
      const entryPoints = Array.from(
        getPackageExports(
          pkgJSON.exports,
          new Set(["browser", "default"]),
          /\.(?:js|mjs|cjs)$/
        )
      );
      if (entryPoints.length) {
        if (pkgJSON.imports) {
          entryPoints.push(
            ...getPackageExports(
              pkgJSON.imports,
              new Set(["browser", "default"]),
              /\.(?:js|mjs|cjs)$/
            )
          );
        }
      }
      const packageName = path.posix.basename(src);
      if (extraPackagesEntries.has(packageName)) {
        entryPoints.push(...extraPackagesEntries.get(packageName)!);
      }
  
      if (!entryPoints.length) {
        throw new Error("Please specify the entry points for package: " + src);
      }
  
      const inputs = entryPoints.map(lib => {
        // Prefix `./` to make it explicitly a relative path, otherwise rollup will
        // try to resolve `eslint/babel-eslint-parser` from node_modules
        const input = "./" + path.join(src, lib.replace("/lib/", "/src/"));
        if (jsPackages.has(packageName)) {
          return input;
        }
        return input.replace(/(\.c)?js$/, "$1ts");
      });
  
      yield {
        src,
        format: "esm",
        dest: "lib",
        inputs,
        pkgJSON,
      };
    }
  }
  
  type PackageInfo = {
    src: string;
    format: "cjs" | "esm" | "umd";
    dest: string;
    inputs: string[];
    name?: string;
    filename?: string;
    envName?: string;
    pkgJSON: any;
  };
  
  const libBundles: PackageInfo[] = Array.from(libBundlesIterator());
  
  const dtsBundles = Array.from(
    packagesIterator(
      new Set([
        // CLIs
        "babel-cli",
        "babel-build-external-helpers",
        "babel-node",
        // This will be just JSON
        "babel-compat-data",
        "babel-helper-globals",
        // Not meant to be consumed manually
        "babel-runtime",
        "babel-runtime-corejs3",
        // TODO: Add type definitions
        "babel-register",
  
        "babel-eslint-plugin",
        "babel-eslint-plugin-development",
        "babel-eslint-plugin-development-internal",
        // Not meant to be consumed manually
        "babel-eslint-shared-fixtures",
        "babel-eslint-tests",
      ])
    )
  );
  
  const standaloneBundle: PackageInfo[] = [
    {
      src: "packages/babel-standalone",
      format: "umd" as const,
      name: "Babel",
      filename: "babel.js",
      dest: "",
      envName: "standalone",
      inputs: [getIndexFromPackage("packages/babel-standalone")],
      pkgJSON: JSON.parse(
        fs.readFileSync(
          new URL(`./packages/babel-standalone/package.json`, import.meta.url),
          "utf-8"
        )
      ),
    },
  ];
  
  gulp.task("generate-helper-globals-data", () => {
    log("Generating @babel/helper-globals data");
  
    return Promise.all([
      generateHelperGlobalsData("browser-upper.json"),
      generateHelperGlobalsData("builtin-lower.json"),
      generateHelperGlobalsData("builtin-upper.json"),
    ]);
  });
  
  gulp.task("generate-type-helpers", () => {
    log("Generating @babel/types and @babel/traverse dynamic functions");
  
    return Promise.all([
      generateTypeHelpers("asserts"),
      generateTypeHelpers("builders", "lowercase.ts"),
      generateTypeHelpers("builders", "uppercase.ts"),
      generateTypeHelpers("builders"),
      generateTypeHelpers("constants"),
      generateTypeHelpers("validators"),
      generateTypeHelpers("ast-types"),
      generateTraverseHelpers("asserts", "path"),
      generateTraverseHelpers("validators", "path"),
      generateTraverseHelpers("visitor-types"),
    ]);
  });
  
  gulp.task("generate-runtime-helpers", async () => {
    log("Generating @babel/helpers runtime helpers");
  
    await generateHelpers(
      `./packages/babel-helpers/scripts/generate-helpers.js`,
      `./packages/babel-helpers/src/`,
      "helpers-generated.ts",
      "@babel/helpers"
    );
  });
  
  gulp.task("generate-standalone", () => generateStandalone());
  
  gulp.task("materialize-babel-8-src", () =>
    applyBabelToGlob(new Glob(defaultSourcesGlob, { posix: true }), {
      plugins: [
        [
          babelPluginToggleBooleanFlag,
          { name: "process.env.BABEL_8_BREAKING", value: true },
        ],
        [
          babelPluginToggleBooleanFlag,
          { name: "USE_ESM", value: true },
          "USE_ESM",
        ],
        (api: any) => ({
          visitor: {
            SpreadElement: {
              exit(path: any) {
                const { argument } = path.node;
                if (api.types.isObjectExpression(argument)) {
                  path.replaceWithMultiple(argument.properties);
                }
              },
            },
          },
        }),
      ],
      parserOpts: {
        plugins: ["typescript", "decorators", "decoratorAutoAccessors"],
      },
      generatorOpts: {
        shouldPrintComment: (comment: string) =>
          !comment.includes("@ts-ignore(Babel 7 vs Babel 8)"),
      },
    })
  );
  
  gulp.task("materialize-babel-8-tests", () =>
    applyBabelToGlob(
      new Glob(`${defaultPackagesGlob}/test/**/{*.js,*.cjs,*.mjs}`, {
        ignore: [
          `**/test/fixtures/**`,
          `**/test/regenerator-fixtures/**`,
          `**/test/tmp/**`,
          `**/test/node_modules/**`,
          `**/babel-parser/test/expressions/**`,
        ],
      }),
      {
        plugins: [
          ({ types: t }: typeof import("@babel/core")) => ({
            visitor: {
              CallExpression(path: NodePath<types.CallExpression>) {
                if (path.node.arguments.length < 2) return;
                if (!path.parentPath.isExpressionStatement()) return;
  
                let callee = path.get("callee");
                while (callee.isMemberExpression() || callee.isCallExpression()) {
                  if (callee.isCallExpression()) {
                    callee = callee.get("callee");
                  } else {
                    callee = callee.get("object");
                  }
                }
                if (!callee.isIdentifier()) return;
                switch (callee.node.name) {
                  case "itNoESM":
                  case "itGteNoESM":
                  case "itBabel7":
                  case "itBabel7NoESM":
                  case "itBabel7GteNoESM":
                  case "describeBabel7":
                  case "describeBabel7NoESM":
                    path.remove();
                    break;
  
                  case "itESM":
                  case "itBabel8":
                    callee.replaceWith(t.identifier("it"));
                    break;
  
                  case "describeESM":
                  case "describeBabel8":
                    callee.replaceWith(t.identifier("describe"));
                    break;
                }
              },
              Program: {
                exit(path: NodePath<types.Program>) {
                  path.scope.crawl();
  
                  for (const decl of path.get("body")) {
                    if (!decl.isImportDeclaration()) continue;
                    if (decl.node.source.value !== "$repo-utils") continue;
  
                    for (const spec of decl.get("specifiers")) {
                      if (
                        !path.scope.getBinding(spec.node.local.name)?.referenced
                      ) {
                        spec.remove();
                      }
                    }
                    if (decl.node.specifiers.length === 0) {
                      decl.remove();
                    }
                  }
                },
              },
            },
          }),
        ],
      }
    )
  );
  
  gulp.task("build-rollup", () => buildRollup(libBundles));
  gulp.task("rollup-babel-standalone", () => buildRollup(standaloneBundle, true));
  gulp.task(
    "build-babel-standalone",
    gulp.series("generate-standalone", "rollup-babel-standalone")
  );
  
  gulp.task("bundle-babel-parser-dts", () => buildBabelParserDts());
  gulp.task("bundle-babel-packages-dts", () => buildPackagesDts(dtsBundles));
  gulp.task(
    "bundle-dts",
    gulp.parallel("bundle-babel-parser-dts", "bundle-babel-packages-dts")
  );
  
  gulp.task("build-babel", () => buildBabel(true, /* exclude */ libBundles));
  
  gulp.task(
    "build",
    gulp.series(
      gulp.parallel("build-rollup", "build-babel"),
      gulp.parallel(
        "generate-type-helpers",
        "generate-runtime-helpers",
        "generate-helper-globals-data"
      ),
      // rebuild @babel/types and @babel/helpers since
      // type-helpers and generated helpers may be changed
      gulp.parallel("build-rollup", "build-babel"),
      "generate-standalone"
    )
  );
  
  gulp.task("default", gulp.series("build"));
  
  // First build on worker processes for compilation speed
  gulp.task("build-no-bundle", () => buildBabel(true));
  // Incremental builds take place in main process
  gulp.task("build-no-bundle-watch", () => buildBabel(false));
  
  gulp.task(
    "build-dev",
    gulp.series(
      "build-no-bundle",
      gulp.parallel(
        "generate-standalone",
        "generate-runtime-helpers",
        "generate-helper-globals-data",
        gulp.series(
          "generate-type-helpers",
          // rebuild @babel/types since type-helpers may be changed
          "build-no-bundle"
        ),
        "bundle-babel-parser-dts"
      )
    )
  );
  
  function watch() {
    gulp.watch(defaultSourcesGlob, gulp.task("build-no-bundle-watch"));
    gulp.watch(babelStandalonePluginConfigGlob, gulp.task("generate-standalone"));
    gulp.watch(buildTypingsWatchGlob, gulp.task("generate-type-helpers"));
    gulp.watch(
      ["./packages/babel-helpers/src/helpers/*"],
      gulp.task("generate-runtime-helpers")
    );
    gulp.watch(
      ["./packages/babel-parser/src/**/*.ts"],
      gulp.task("bundle-babel-parser-dts")
    );
  }
  
  gulp.task(
    "watch",
    process.env.WATCH_SKIP_BUILD ? watch : gulp.series("build-dev", watch)
  );
  