Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/commands/emulators-exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ module.exports = new Command("emulators:exec <script>")
}
let exitCode = 0;
try {
await controller.startAll(options);
await controller.startAll(options, /* noGui = */ true);
exitCode = await runScript(script, extraEnv);
} catch (e) {
logger.debug("Error in emulators:exec", e);
Expand Down
13 changes: 12 additions & 1 deletion src/emulator/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import * as url from "url";
import { Address, Emulators } from "./types";

const DEFAULT_PORTS: { [s in Emulators]: number } = {
hub: 4000,
gui: 4000,
hub: 4400,
hosting: 5000,
functions: 5001,
firestore: 8080,
Expand Down Expand Up @@ -59,6 +60,16 @@ export class Constants {
return { host, port };
}

static description(name: Emulators): string {
if (name === Emulators.HUB) {
return "emulator hub";
} else if (name === Emulators.GUI) {
return "emulator GUI";
} else {
return `${name} emulator`;
}
}

private static normalizeHost(host: string): string {
let normalized = host;
if (!normalized.startsWith("http")) {
Expand Down
62 changes: 53 additions & 9 deletions src/emulator/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ import * as logger from "../logger";
import * as utils from "../utils";
import * as track from "../track";
import { EmulatorRegistry } from "../emulator/registry";
import { EmulatorInstance, Emulators, ALL_SERVICE_EMULATORS } from "../emulator/types";
import {
EmulatorInstance,
Emulators,
ALL_SERVICE_EMULATORS,
EMULATORS_SUPPORTED_BY_GUI,
} from "../emulator/types";
import { Constants } from "../emulator/constants";
import { FunctionsEmulator } from "../emulator/functionsEmulator";
import { DatabaseEmulator, DatabaseEmulatorArgs } from "../emulator/databaseEmulator";
Expand All @@ -21,6 +26,8 @@ import { PubsubEmulator } from "./pubsubEmulator";
import * as commandUtils from "./commandUtils";
import { EmulatorHub } from "./hub";
import { ExportMetadata, HubExport } from "./hubExport";
import { EmulatorGUI } from "./gui";
import previews = require("../previews");

export async function checkPortOpen(port: number, host: string): Promise<boolean> {
try {
Expand Down Expand Up @@ -52,7 +59,7 @@ export async function startEmulator(instance: EmulatorInstance): Promise<void> {
const portOpen = await checkPortOpen(port, host);
if (!portOpen) {
await cleanShutdown();
const description = name === Emulators.HUB ? "emulator hub" : `${name} emulator`;
const description = Constants.description(name);
utils.logWarning(`Port ${port} is not open on ${host}, could not start ${description}.`);
utils.logBullet(`To select a different host/port for the emulator, update your "firebase.json":
{
Expand All @@ -74,15 +81,14 @@ export async function cleanShutdown(): Promise<boolean> {
utils.logBullet("Shutting down emulators.");

for (const name of EmulatorRegistry.listRunning()) {
const description = name === Emulators.HUB ? "emulator hub" : `${name} emulator`;
utils.logBullet(`Stopping ${description}`);
utils.logBullet(`Stoppping ${Constants.description(name)}`);
await EmulatorRegistry.stop(name);
}

return true;
}

export function filterEmulatorTargets(options: any): string[] {
export function filterEmulatorTargets(options: any): Emulators[] {
let targets = ALL_SERVICE_EMULATORS.filter((e) => {
return options.config.has(e) || options.config.has(`emulators.${e}`);
});
Expand All @@ -96,15 +102,23 @@ export function filterEmulatorTargets(options: any): string[] {

export function shouldStart(options: any, name: Emulators): boolean {
if (name === Emulators.HUB) {
// The hub only starts if we know the project ID
// The hub only starts if we know the project ID.
return !!options.project;
}

const targets = filterEmulatorTargets(options);
if (name === Emulators.GUI) {
// The GUI only starts if we know the project ID AND at least one emulator
// supported by GUI is launching.
return (
previews.emulatorgui &&
!!options.project &&
targets.some((target) => EMULATORS_SUPPORTED_BY_GUI.indexOf(target) >= 0)
);
}
return targets.indexOf(name) >= 0;
}

export async function startAll(options: any): Promise<void> {
export async function startAll(options: any, noGui: boolean = false): Promise<void> {
// Emulators config is specified in firebase.json as:
// "emulators": {
// "firestore": {
Expand Down Expand Up @@ -148,7 +162,9 @@ export async function startAll(options: any): Promise<void> {
if (hubPort != hubAddr.port) {
utils.logLabeledWarning(
"emulators",
`Emulator hub unable to start on port ${hubAddr.port}, starting on ${hubPort}`
`${Constants.description(Emulators.HUB)} unable to start on port ${
hubAddr.port
}, starting on ${hubPort}`
);
}

Expand Down Expand Up @@ -320,6 +336,34 @@ export async function startAll(options: any): Promise<void> {
await startEmulator(pubsubEmulator);
}

if (!noGui && shouldStart(options, Emulators.GUI)) {
// For the GUI we actually will find any available port
// since we don't want to explode if the GUI can't start on 3000.
const guiAddr = Constants.getAddress(Emulators.GUI, options);
const guiPort = await pf.getPortPromise({
host: guiAddr.host,
port: guiAddr.port,
stopPort: guiAddr.port + 100,
});

if (guiPort != guiAddr.port) {
utils.logLabeledWarning(
Emulators.GUI,
`${Constants.description(Emulators.GUI)} unable to start on port ${
guiAddr.port
}, starting on ${guiPort}`
);
}

const gui = new EmulatorGUI({
projectId,
host: guiAddr.host,
port: guiPort,
auto_download: true,
});
await startEmulator(gui);
}

const running = EmulatorRegistry.listRunning();
for (const name of running) {
const instance = EmulatorRegistry.get(name);
Expand Down
56 changes: 56 additions & 0 deletions src/emulator/gui.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { EmulatorInstance, EmulatorInfo, Emulators } from "./types";
import * as javaEmulators from "../serve/javaEmulators";
import { EmulatorRegistry } from "./registry";
import { EmulatorHub } from "./hub";
import { FirebaseError } from "../error";
import { Constants } from "./constants";

export interface EmulatorGUIOptions {
port: number;
host: string;
projectId: string;
auto_download?: boolean;
}

export class EmulatorGUI implements EmulatorInstance {
constructor(private args: EmulatorGUIOptions) {}

start(): Promise<void> {
if (!EmulatorRegistry.isRunning(Emulators.HUB)) {
throw new FirebaseError(
`Cannot start ${Constants.description(Emulators.GUI)} without ${Constants.description(
Emulators.HUB
)}!`
);
}
const hubInfo = EmulatorRegistry.get(Emulators.HUB)!.getInfo();
const { auto_download, host, port, projectId } = this.args;
const env: NodeJS.ProcessEnv = {
HOST: host.toString(),
PORT: port.toString(),
GCLOUD_PROJECT: projectId,
[EmulatorHub.EMULATOR_HUB_ENV]: `${hubInfo.host}:${hubInfo.port}`,
};

return javaEmulators.start(Emulators.GUI, { auto_download }, env);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably out of scope here but seems officially time to rename javaEmulators to something like BinaryEmulators

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be happy to follow up with another PR. It's not that hard but it does create a lot of noisy diffs that I want to keep separate.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

async connect(): Promise<void> {
return;
}

stop(): Promise<void> {
return javaEmulators.stop(Emulators.GUI);
}

getInfo(): EmulatorInfo {
return {
host: this.args.host,
port: this.args.port,
};
}

getName(): Emulators {
return Emulators.GUI;
}
}
19 changes: 7 additions & 12 deletions src/emulator/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ALL_EMULATORS, EmulatorInstance, Emulators } from "./types";
import { FirebaseError } from "../error";
import * as utils from "../utils";
import * as controller from "./controller";
import { Constants } from "./constants";

/**
* Static registry for running emulators to discover each other.
Expand All @@ -13,8 +14,9 @@ import * as controller from "./controller";
*/
export class EmulatorRegistry {
static async start(instance: EmulatorInstance): Promise<void> {
const description = Constants.description(instance.getName());
if (this.isRunning(instance.getName())) {
throw new FirebaseError(`Emulator ${instance.getName()} is already running!`, {});
throw new FirebaseError(`${description} is already running!`, {});
}

// Start the emulator and wait for it to grab its assigned port.
Expand All @@ -25,17 +27,10 @@ export class EmulatorRegistry {

this.set(instance.getName(), instance);

if (instance.getName() === Emulators.HUB) {
utils.logLabeledSuccess(
"emulators",
`Emulator hub started at ${clc.bold.underline(`http://${info.host}:${info.port}`)}`
);
} else {
utils.logLabeledSuccess(
instance.getName(),
`Emulator started at ${clc.bold.underline(`http://${info.host}:${info.port}`)}`
);
}
utils.logLabeledSuccess(
instance.getName(),
`${description} started at ${clc.bold.underline(`http://${info.host}:${info.port}`)}`
);
}

static async stop(name: Emulators): Promise<void> {
Expand Down
18 changes: 15 additions & 3 deletions src/emulator/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,20 @@ export enum Emulators {
DATABASE = "database",
HOSTING = "hosting",
PUBSUB = "pubsub",
GUI = "gui",
}

export type JavaEmulators = Emulators.FIRESTORE | Emulators.DATABASE | Emulators.PUBSUB;
export const JAVA_EMULATORS = [Emulators.FIRESTORE, Emulators.DATABASE, Emulators.PUBSUB];
export type JavaEmulators =
| Emulators.FIRESTORE
| Emulators.DATABASE
| Emulators.PUBSUB
| Emulators.GUI;
export const JAVA_EMULATORS = [
Emulators.FIRESTORE,
Emulators.DATABASE,
Emulators.PUBSUB,
Emulators.GUI,
];

export type ImportExportEmulators = Emulators.FIRESTORE;
export const IMPORT_EXPORT_EMULATORS = [Emulators.FIRESTORE];
Expand All @@ -24,8 +34,10 @@ export const ALL_SERVICE_EMULATORS = [
Emulators.PUBSUB,
];

export const EMULATORS_SUPPORTED_BY_GUI = [Emulators.DATABASE];

// TODO: Is there a way we can just allow iteration over the enum?
export const ALL_EMULATORS = [Emulators.HUB, ...ALL_SERVICE_EMULATORS];
export const ALL_EMULATORS = [Emulators.HUB, Emulators.GUI, ...ALL_SERVICE_EMULATORS];

export function isJavaEmulator(value: string): value is JavaEmulators {
return isEmulator(value) && JAVA_EMULATORS.indexOf(value) >= 0;
Expand Down
1 change: 1 addition & 0 deletions src/previews.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ var previews = _.assign(
{
// insert previews here...
rtdbrules: false,
emulatorgui: false,
},
configstore.get("previews")
);
Expand Down
Loading