export async function startAll()

in src/emulator/controller.ts [312:720]


export async function startAll(options: EmulatorOptions, showUI: boolean = true): Promise<void> {
  // Emulators config is specified in firebase.json as:
  // "emulators": {
  //   "firestore": {
  //     "host": "localhost",
  //     "port": "9005"
  //   },
  //   // ...
  // }
  //
  // The list of emulators to start is filtered two ways:
  // 1) The service must have a top-level entry in firebase.json or an entry in the emulators{} object
  // 2) If the --only flag is passed, then this list is the intersection
  const targets = filterEmulatorTargets(options);
  options.targets = targets;

  if (targets.length === 0) {
    throw new FirebaseError(
      `No emulators to start, run ${clc.bold("firebase init emulators")} to get started.`
    );
  }
  const hubLogger = EmulatorLogger.forEmulator(Emulators.HUB);
  hubLogger.logLabeled("BULLET", "emulators", `Starting emulators: ${targets.join(", ")}`);

  const projectId: string = getProjectId(options) || ""; // TODO: Next breaking change, consider making this fall back to demo project.
  if (Constants.isDemoProject(projectId)) {
    hubLogger.logLabeled(
      "BULLET",
      "emulators",
      `Detected demo project ID "${projectId}", emulated services will use a demo configuration and attempts to access non-emulated services for this project will fail.`
    );
  }

  const onlyOptions: string = options.only;
  if (onlyOptions) {
    const requested: string[] = onlyOptions.split(",").map((o) => {
      return o.split(":")[0];
    });
    const ignored = _.difference(requested, targets);

    for (const name of ignored) {
      if (isEmulator(name)) {
        EmulatorLogger.forEmulator(name).logLabeled(
          "WARN",
          name,
          `Not starting the ${clc.bold(name)} emulator, make sure you have run ${clc.bold(
            "firebase init"
          )}.`
        );
      } else {
        // this should not work:
        // firebase emulators:start --only doesnotexit
        throw new FirebaseError(
          `${name} is not a valid emulator name, valid options are: ${JSON.stringify(
            ALL_SERVICE_EMULATORS
          )}`,
          { exit: 1 }
        );
      }
    }
  }

  if (shouldStart(options, Emulators.HUB)) {
    const hubAddr = await getAndCheckAddress(Emulators.HUB, options);
    const hub = new EmulatorHub({ projectId, ...hubAddr });

    // Log the command for analytics, we only report this for "hub"
    // since we originally mistakenly reported emulators:start events
    // for each emulator, by reporting the "hub" we ensure that our
    // historical data can still be viewed.
    track("emulators:start", "hub");
    await startEmulator(hub);
  }

  // Parse export metadata
  let exportMetadata: ExportMetadata = {
    version: "unknown",
  };
  if (options.import) {
    utils.assertIsString(options.import);
    const importDir = path.resolve(options.import);
    const foundMetadata = findExportMetadata(importDir);
    if (foundMetadata) {
      exportMetadata = foundMetadata;
    } else {
      hubLogger.logLabeled(
        "WARN",
        "emulators",
        `Could not find import/export metadata file, ${clc.bold("skipping data import!")}`
      );
    }
  }

  if (shouldStart(options, Emulators.FUNCTIONS)) {
    const functionsLogger = EmulatorLogger.forEmulator(Emulators.FUNCTIONS);
    const functionsAddr = await getAndCheckAddress(Emulators.FUNCTIONS, options);
    const projectId = needProjectId(options);

    utils.assertDefined(options.config.src.functions);
    utils.assertDefined(
      options.config.src.functions.source,
      "Error: 'functions.source' is not defined"
    );

    utils.assertIsStringOrUndefined(options.extensionDir);
    const functionsDir = path.join(
      options.extensionDir || options.config.projectDir,
      options.config.src.functions.source
    );

    let inspectFunctions: number | undefined;
    if (options.inspectFunctions) {
      inspectFunctions = commandUtils.parseInspectionPort(options);

      // TODO(samstern): Add a link to documentation
      functionsLogger.logLabeled(
        "WARN",
        "functions",
        `You are running the functions emulator in debug mode (port=${inspectFunctions}). This means that functions will execute in sequence rather than in parallel.`
      );
    }

    // Warn the developer that the Functions emulator can call out to production.
    const emulatorsNotRunning = ALL_SERVICE_EMULATORS.filter((e) => {
      return e !== Emulators.FUNCTIONS && !shouldStart(options, e);
    });
    if (emulatorsNotRunning.length > 0 && !Constants.isDemoProject(projectId)) {
      functionsLogger.logLabeled(
        "WARN",
        "functions",
        `The following emulators are not running, calls to these services from the Functions emulator will affect production: ${clc.bold(
          emulatorsNotRunning.join(", ")
        )}`
      );
    }

    const account = getProjectDefaultAccount(options.projectRoot);
    // TODO: Go read firebase.json for extensions and add them to emualtableBackends.
    const emulatableBackends: EmulatableBackend[] = [
      {
        functionsDir,
        env: {
          ...options.extensionEnv,
        },
        predefinedTriggers: options.extensionTriggers as ParsedTriggerDefinition[] | undefined,
        nodeMajorVersion: parseRuntimeVersion(
          options.extensionNodeVersion || options.config.get("functions.runtime")
        ),
      },
    ];
    const functionsEmulator = new FunctionsEmulator({
      projectId,
      emulatableBackends,
      account,
      host: functionsAddr.host,
      port: functionsAddr.port,
      debugPort: inspectFunctions,
    });
    await startEmulator(functionsEmulator);
  }

  if (shouldStart(options, Emulators.FIRESTORE)) {
    const firestoreLogger = EmulatorLogger.forEmulator(Emulators.FIRESTORE);
    const firestoreAddr = await getAndCheckAddress(Emulators.FIRESTORE, options);

    const args: FirestoreEmulatorArgs = {
      host: firestoreAddr.host,
      port: firestoreAddr.port,
      projectId,
      auto_download: true,
    };

    if (exportMetadata.firestore) {
      utils.assertIsString(options.import);
      const importDirAbsPath = path.resolve(options.import);
      const exportMetadataFilePath = path.resolve(
        importDirAbsPath,
        exportMetadata.firestore.metadata_file
      );

      firestoreLogger.logLabeled(
        "BULLET",
        "firestore",
        `Importing data from ${exportMetadataFilePath}`
      );
      args.seed_from_export = exportMetadataFilePath;
    }

    const config = options.config;
    const rulesLocalPath = config.src.firestore?.rules;
    let rulesFileFound = false;
    if (rulesLocalPath) {
      const rules: string = config.path(rulesLocalPath);
      rulesFileFound = fs.existsSync(rules);
      if (rulesFileFound) {
        args.rules = rules;
      } else {
        firestoreLogger.logLabeled(
          "WARN",
          "firestore",
          `Cloud Firestore rules file ${clc.bold(rules)} specified in firebase.json does not exist.`
        );
      }
    } else {
      firestoreLogger.logLabeled(
        "WARN",
        "firestore",
        "Did not find a Cloud Firestore rules file specified in a firebase.json config file."
      );
    }

    if (!rulesFileFound) {
      firestoreLogger.logLabeled(
        "WARN",
        "firestore",
        "The emulator will default to allowing all reads and writes. Learn more about this option: https://firebase.google.com/docs/emulator-suite/install_and_configure#security_rules_configuration."
      );
    }

    const firestoreEmulator = new FirestoreEmulator(args);
    await startEmulator(firestoreEmulator);
  }

  if (shouldStart(options, Emulators.DATABASE)) {
    const databaseLogger = EmulatorLogger.forEmulator(Emulators.DATABASE);
    const databaseAddr = await getAndCheckAddress(Emulators.DATABASE, options);

    const args: DatabaseEmulatorArgs = {
      host: databaseAddr.host,
      port: databaseAddr.port,
      projectId,
      auto_download: true,
    };

    // Try to fetch the default RTDB instance for a project, but don't hard-fail if we
    // can't because the user may be using a fake project.
    try {
      if (!options.instance) {
        options.instance = await getDefaultDatabaseInstance(options);
      }
    } catch (e: any) {
      databaseLogger.log(
        "DEBUG",
        `Failed to retrieve default database instance: ${JSON.stringify(e)}`
      );
    }

    const rc = dbRulesConfig.normalizeRulesConfig(
      dbRulesConfig.getRulesConfig(projectId, options),
      options
    );
    logger.debug("database rules config: ", JSON.stringify(rc));

    args.rules = rc;

    if (rc.length === 0) {
      databaseLogger.logLabeled(
        "WARN",
        "database",
        "Did not find a Realtime Database rules file specified in a firebase.json config file. The emulator will default to allowing all reads and writes. Learn more about this option: https://firebase.google.com/docs/emulator-suite/install_and_configure#security_rules_configuration."
      );
    } else {
      for (const c of rc) {
        const rules: string = c.rules;
        if (!fs.existsSync(rules)) {
          databaseLogger.logLabeled(
            "WARN",
            "database",
            `Realtime Database rules file ${clc.bold(
              rules
            )} specified in firebase.json does not exist.`
          );
        }
      }
    }

    const databaseEmulator = new DatabaseEmulator(args);
    await startEmulator(databaseEmulator);

    if (exportMetadata.database) {
      utils.assertIsString(options.import);
      const importDirAbsPath = path.resolve(options.import);
      const databaseExportDir = path.resolve(importDirAbsPath, exportMetadata.database.path);

      const files = fs.readdirSync(databaseExportDir).filter((f) => f.endsWith(".json"));
      for (const f of files) {
        const fPath = path.join(databaseExportDir, f);
        const ns = path.basename(f, ".json");
        await databaseEmulator.importData(ns, fPath);
      }
    }
  }

  if (shouldStart(options, Emulators.AUTH)) {
    if (!projectId) {
      throw new FirebaseError(
        `Cannot start the ${Constants.description(
          Emulators.AUTH
        )} without a project: run 'firebase init' or provide the --project flag`
      );
    }

    const authAddr = await getAndCheckAddress(Emulators.AUTH, options);
    const authEmulator = new AuthEmulator({
      host: authAddr.host,
      port: authAddr.port,
      projectId,
    });
    await startEmulator(authEmulator);

    if (exportMetadata.auth) {
      utils.assertIsString(options.import);
      const importDirAbsPath = path.resolve(options.import);
      const authExportDir = path.resolve(importDirAbsPath, exportMetadata.auth.path);

      await authEmulator.importData(authExportDir, projectId);
    }
  }

  if (shouldStart(options, Emulators.PUBSUB)) {
    if (!projectId) {
      throw new FirebaseError(
        "Cannot start the Pub/Sub emulator without a project: run 'firebase init' or provide the --project flag"
      );
    }

    const pubsubAddr = await getAndCheckAddress(Emulators.PUBSUB, options);
    const pubsubEmulator = new PubsubEmulator({
      host: pubsubAddr.host,
      port: pubsubAddr.port,
      projectId,
      auto_download: true,
    });
    await startEmulator(pubsubEmulator);
  }

  if (shouldStart(options, Emulators.STORAGE)) {
    const storageAddr = await getAndCheckAddress(Emulators.STORAGE, options);
    const storageConfig = options.config.data.storage;

    if (!storageConfig?.rules) {
      throw new FirebaseError(
        "Cannot start the Storage emulator without rules file specified in firebase.json: run 'firebase init' and set up your Storage configuration"
      );
    }

    const storageEmulator = new StorageEmulator({
      host: storageAddr.host,
      port: storageAddr.port,
      projectId: projectId,
      rules: options.config.path(storageConfig.rules),
    });
    await startEmulator(storageEmulator);

    if (exportMetadata.storage) {
      utils.assertIsString(options.import);
      const importDirAbsPath = path.resolve(options.import);
      const storageExportDir = path.resolve(importDirAbsPath, exportMetadata.storage.path);
      storageEmulator.storageLayer.import(storageExportDir);
    }
  }

  // Hosting emulator needs to start after all of the others so that we can detect
  // which are running and call useEmulator in __init.js
  if (shouldStart(options, Emulators.HOSTING)) {
    const hostingAddr = await getAndCheckAddress(Emulators.HOSTING, options);
    const hostingEmulator = new HostingEmulator({
      host: hostingAddr.host,
      port: hostingAddr.port,
      options,
    });

    await startEmulator(hostingEmulator);
  }

  if (showUI && !shouldStart(options, Emulators.UI)) {
    hubLogger.logLabeled(
      "WARN",
      "emulators",
      "The Emulator UI requires a project ID to start. Configure your default project with 'firebase use' or pass the --project flag."
    );
  }

  if (showUI && shouldStart(options, Emulators.UI)) {
    const loggingAddr = await getAndCheckAddress(Emulators.LOGGING, options);
    const loggingEmulator = new LoggingEmulator({
      host: loggingAddr.host,
      port: loggingAddr.port,
    });

    await startEmulator(loggingEmulator);

    const uiAddr = await getAndCheckAddress(Emulators.UI, options);
    const ui = new EmulatorUI({
      projectId: projectId,
      auto_download: true,
      ...uiAddr,
    });
    await startEmulator(ui);
  }

  const running = EmulatorRegistry.listRunning();
  for (const name of running) {
    const instance = EmulatorRegistry.get(name);
    if (instance) {
      await instance.connect();
    }
  }
}