Repository: firebase/firebase-tools
Pull request #1594 (2019-08-12 18:12:33)

Add some Node.js 10 emulator runtime variables
# Description

We're making use of `FUNCTION_TARGET` in production. It would help to have it locally in the emulator too.

This change adds some of the predefined environmental variables for Node.js 10 and subsequent runtimes.
https://cloud.google.com/functions/docs/env-var#nodejs_10_and_subsequent_runtimes

coveralls: 
[![Coverage Status](https://coveralls.io/builds/25115084/badge)](https://coveralls.io/builds/25115084)

Coverage remained the same at 64.869% when pulling **bdf5f27d3fd22ef8300de2f5cb2cb598b82b878a on 29ki:nodejs10-emulator-runtime-env** into **0421f404dd9fef7423f44a3bade3031d7b6e1272 on firebase:master**.

bkendall: @abeisgoat or @samtstern will be the appropriate folks to take a look at this. Thanks for the PR!
samtstern: @op really sorry for the slow review here!  I was on vacation for 2.5 weeks and am just catching up.
samtstern: Oh actually one more thing: please add a line to the `CHANGELOG.md` file to say what feature this adds.  Let me know if you're not sure what to put.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0add9126a..2b1e0e29d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,2 +1,3 @@
 * Ensures `auth:export` results are fully flushed to the output file.
 * Fix bug in Firestore emulator where concurrent requests for the same transaction would sometimes hang.
+* Set the predefined runtime environment variables in the functions emulator
diff --git a/src/emulator/functionsEmulator.ts b/src/emulator/functionsEmulator.ts
index 2eef070aa..a9f16f3c6 100644
--- a/src/emulator/functionsEmulator.ts
+++ b/src/emulator/functionsEmulator.ts
@@ -19,6 +19,7 @@ import { ChildProcess, spawnSync } from "child_process";
 import {
   EmulatedTriggerDefinition,
   EmulatedTriggerMap,
+  EmulatedTriggerType,
   FunctionsRuntimeBundle,
   FunctionsRuntimeFeatures,
   getFunctionRegion,
@@ -125,6 +126,7 @@ export class FunctionsEmulator implements EmulatorInstance {
       const runtime = FunctionsEmulator.startFunctionRuntime(
         bundleTemplate,
         triggerId,
+        EmulatedTriggerType.BACKGROUND,
         nodeBinary,
         proto
       );
@@ -166,7 +168,12 @@ export class FunctionsEmulator implements EmulatorInstance {
 
       const reqBody = (req as RequestWithRawBody).rawBody;
 
-      const runtime = FunctionsEmulator.startFunctionRuntime(bundleTemplate, triggerId, nodeBinary);
+      const runtime = FunctionsEmulator.startFunctionRuntime(
+        bundleTemplate,
+        triggerId,
+        EmulatedTriggerType.HTTPS,
+        nodeBinary
+      );
 
       runtime.events.on("log", (el: EmulatorLog) => {
         if (el.level === "FATAL") {
@@ -258,6 +265,7 @@ export class FunctionsEmulator implements EmulatorInstance {
   static startFunctionRuntime(
     bundleTemplate: FunctionsRuntimeBundle,
     triggerId: string,
+    triggerType: EmulatedTriggerType,
     nodeBinary: string,
     proto?: any,
     runtimeOpts?: InvokeRuntimeOpts
@@ -270,6 +278,7 @@ export class FunctionsEmulator implements EmulatorInstance {
       },
       proto,
       triggerId,
+      triggerType,
     };
 
     const runtime = InvokeRuntime(nodeBinary, runtimeBundle, runtimeOpts || {});
@@ -677,6 +686,7 @@ You can probably fix this by running "npm install ${
       cwd: this.functionsDir,
       projectId: this.projectId,
       triggerId: "",
+      triggerType: undefined,
       ports: {
         firestore: EmulatorRegistry.getPort(Emulators.FIRESTORE),
         database: EmulatorRegistry.getPort(Emulators.DATABASE),
diff --git a/src/emulator/functionsEmulatorRuntime.ts b/src/emulator/functionsEmulatorRuntime.ts
index 993ff2b7a..0c86e8bc3 100644
--- a/src/emulator/functionsEmulatorRuntime.ts
+++ b/src/emulator/functionsEmulatorRuntime.ts
@@ -4,6 +4,7 @@ import {
   EmulatedTrigger,
   EmulatedTriggerDefinition,
   EmulatedTriggerMap,
+  EmulatedTriggerType,
   findModuleRoot,
   FunctionsRuntimeBundle,
   FunctionsRuntimeFeatures,
@@ -72,6 +73,7 @@ function makeFakeCredentials(): any {
 }
 
 interface PackageJSON {
+  engines?: { node?: string };
   dependencies: { [name: string]: any };
   devDependencies: { [name: string]: any };
 }
@@ -287,6 +289,7 @@ function requirePackageJson(frb: FunctionsRuntimeBundle): PackageJSON | undefine
   try {
     const pkg = require(`${frb.cwd}/package.json`);
     developerPkgJSON = {
+      engines: pkg.engines || {},
       dependencies: pkg.dependencies || {},
       devDependencies: pkg.devDependencies || {},
     };
@@ -703,6 +706,28 @@ function InitializeEnvironmentalVariables(frb: FunctionsRuntimeBundle): void {
     storageBucket: process.env.STORAGE_BUCKET_URL || `${process.env.GCLOUD_PROJECT}.appspot.com`,
     projectId: process.env.GCLOUD_PROJECT,
   });
+
+  if (frb.triggerId) {
+    // Runtime values are based on information from the bundle. Proper information for this is
+    // available once the target code has been loaded, which is too late.
+    const service = frb.triggerId || "";
+    const target = service.replace(/-/g, ".");
+    const mode = frb.triggerType === EmulatedTriggerType.BACKGROUND ? "event" : "http";
+
+    // Setup predefined environment variables for Node.js 10 and subsequent runtimes
+    // https://cloud.google.com/functions/docs/env-var
+    const pkg = requirePackageJson(frb);
+    if (pkg && pkg.engines && pkg.engines.node) {
+      const nodeVersion = parseVersionString(pkg.engines.node);
+      if (nodeVersion.major >= 10) {
+        process.env.FUNCTION_TARGET = target;
+        process.env.FUNCTION_SIGNATURE_TYPE = mode;
+        process.env.K_SERVICE = service;
+        process.env.K_REVISION = "1";
+        process.env.PORT = "80";
+      }
+    }
+  }
 }
 
 async function InitializeFunctionsConfigHelper(functionsDir: string): Promise<void> {
diff --git a/src/emulator/functionsEmulatorShared.ts b/src/emulator/functionsEmulatorShared.ts
index e5747c6ee..4eb79a19e 100644
--- a/src/emulator/functionsEmulatorShared.ts
+++ b/src/emulator/functionsEmulatorShared.ts
@@ -8,6 +8,11 @@ import * as path from "path";
 import * as express from "express";
 import * as fs from "fs";
 
+export enum EmulatedTriggerType {
+  BACKGROUND = "BACKGROUND",
+  HTTPS = "HTTPS",
+}
+
 export interface EmulatedTriggerDefinition {
   entryPoint: string;
   name: string;
@@ -33,6 +38,7 @@ export interface FunctionsRuntimeBundle {
   projectId: string;
   proto?: any;
   triggerId?: string;
+  triggerType?: EmulatedTriggerType;
   ports: {
     firestore?: number;
     database?: number;
diff --git a/src/emulator/functionsEmulatorShell.ts b/src/emulator/functionsEmulatorShell.ts
index 6c9a6551c..cc8a8d8f9 100644
--- a/src/emulator/functionsEmulatorShell.ts
+++ b/src/emulator/functionsEmulatorShell.ts
@@ -1,7 +1,11 @@
 import * as _ from "lodash";
 import * as uuid from "uuid";
 import { FunctionsEmulator } from "./functionsEmulator";
-import { EmulatedTriggerDefinition, getFunctionRegion } from "./functionsEmulatorShared";
+import {
+  EmulatedTriggerDefinition,
+  EmulatedTriggerType,
+  getFunctionRegion,
+} from "./functionsEmulatorShared";
 import * as utils from "../utils";
 import * as logger from "../logger";
 import { FirebaseError } from "../error";
@@ -71,6 +75,7 @@ export class FunctionsEmulatorShell implements FunctionsShellController {
     FunctionsEmulator.startFunctionRuntime(
       this.emu.getBaseBundle(),
       name,
+      EmulatedTriggerType.BACKGROUND,
       this.emu.nodeBinary,
       proto
     );


Test patch:
diff --git a/src/test/emulators/functionsEmulator.spec.ts b/src/test/emulators/functionsEmulator.spec.ts
index faa7534fc..9ae5f0634 100644
--- a/src/test/emulators/functionsEmulator.spec.ts
+++ b/src/test/emulators/functionsEmulator.spec.ts
@@ -3,7 +3,10 @@ import { FunctionsEmulator, FunctionsRuntimeInstance } from "../../emulator/func
 import * as supertest from "supertest";
 import { FunctionRuntimeBundles, TIMEOUT_LONG, TIMEOUT_MED } from "./fixtures";
 import * as logger from "../../logger";
-import { FunctionsRuntimeBundle } from "../../emulator/functionsEmulatorShared";
+import {
+  EmulatedTriggerType,
+  FunctionsRuntimeBundle,
+} from "../../emulator/functionsEmulatorShared";
 import * as express from "express";
 
 if ((process.env.DEBUG || "").toLowerCase().indexOf("spec") >= 0) {
@@ -22,10 +25,11 @@ function UseFunctions(triggers: () => {}): void {
   FunctionsEmulator.startFunctionRuntime = (
     bundleTemplate: FunctionsRuntimeBundle,
     triggerId: string,
+    triggerType: EmulatedTriggerType,
     nodeBinary: string,
     proto?: any
   ): FunctionsRuntimeInstance => {
-    return startFunctionRuntime(bundleTemplate, triggerId, nodeBinary, proto, {
+    return startFunctionRuntime(bundleTemplate, triggerId, triggerType, nodeBinary, proto, {
       serializedTriggers,
     });
   };