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();
}
}
}