Skip to content

Commit

Permalink
Merge branch 'master' into samtstern-sdkconfig-logs
Browse files Browse the repository at this point in the history
  • Loading branch information
samtstern committed Oct 11, 2019
2 parents babf4d4 + 77b0182 commit e0e26fd
Show file tree
Hide file tree
Showing 13 changed files with 168 additions and 58 deletions.
3 changes: 0 additions & 3 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,10 @@ module.exports = {
{
"files": ["*.js"],
"rules": {
"guard-for-in": "warn", // TODO(bkendall): remove, allow to error.
"no-extra-boolean-cast": "warn", // TODO(bkendall): remove, allow to error.
"no-invalid-this": "warn", // TODO(bkendall): remove, allow to error.
"no-redeclare": "warn", // TODO(bkendall): remove, allow to error.
"no-undef": "warn", // TODO(bkendall): remove, allow to error.
"no-var": "warn", // TODO(bkendall): remove, allow to error.
"one-var": "warn", // TODO(bkendall): remove, allow to error.
"prefer-rest-params": "warn", // TODO(bkendall): remove, allow to error.
},
},
Expand Down
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
* Added support for managing the Realtime Database setting `strictTriggerValidation`.
* Fixes trigger parser to not rely on prototype methods (#1687).
* Fixes bug where standalone CLI would hang in the Android Studio integrated terminal.
* Fixes bug where standalone CLI would hang in the Android Studio integrated terminal.
* Fixes a bug where accessing refs from background function arguments would point to prod (#1682).
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"build": "tsc",
"build:watch": "tsc --watch",
"clean": "rm -rf lib",
"format": "npm run lint -- --fix",
"format": "npm run lint -- --fix --quiet",
"lint": "eslint '**/*.{js,ts}'",
"mocha": "nyc mocha --opts mocha.opts",
"prepare": "npm run clean && npm run build -- --build tsconfig.publish.json",
Expand Down
3 changes: 2 additions & 1 deletion src/deploy/functions/release.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ module.exports = function(context, options, payload) {
});
var uploadedNames = _.map(functionsInfo, "name");
var functionFilterGroups = helper.getFilterGroups(options);
var deleteReleaseNames, existingScheduledFunctions;
var deleteReleaseNames;
var existingScheduledFunctions;

delete payload.functions;
return gcp.cloudfunctions
Expand Down
6 changes: 3 additions & 3 deletions src/deploy/hosting/hashcache.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function cachePath(cwd, name) {
exports.load = function(cwd, name) {
try {
const out = {};
lines = fs.readFileSync(cachePath(cwd, name), {
const lines = fs.readFileSync(cachePath(cwd, name), {
encoding: "utf8",
});
lines.split("\n").forEach(function(line) {
Expand All @@ -32,9 +32,9 @@ exports.load = function(cwd, name) {
exports.dump = function(cwd, name, data) {
let st = "";
let count = 0;
for (let path in data) {
for (const [path, d] of data) {
count++;
st += path + "," + data[path].mtime + "," + data[path].hash + "\n";
st += path + "," + d.mtime + "," + d.hash + "\n";
}
try {
fs.outputFileSync(cachePath(cwd, name), st, { encoding: "utf8" });
Expand Down
6 changes: 3 additions & 3 deletions src/deploy/hosting/uploader.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class Uploader {
this.fileCount = this.files.length;

this.cache = hashcache.load(this.projectRoot, this.hashcacheName());
this.cacheNew = {};
this.cacheNew = new Map();

this.sizeMap = {};
this.hashMap = {};
Expand Down Expand Up @@ -141,7 +141,7 @@ class Uploader {
this.sizeMap[filePath] = stats.size;
const cached = this.cache[filePath];
if (cached && cached.mtime === mtime) {
this.cacheNew[filePath] = cached;
this.cacheNew.set(filePath, cached);
this.addHash(filePath, cached.hash);
return Promise.resolve();
}
Expand All @@ -155,7 +155,7 @@ class Uploader {
return new Promise(function(resolve, reject) {
fstream.on("end", function() {
const hashVal = hash.read().toString("hex");
self.cacheNew[filePath] = { mtime: mtime, hash: hashVal };
self.cacheNew.set(filePath, { mtime: mtime, hash: hashVal });
self.addHash(filePath, hashVal);
resolve();
});
Expand Down
17 changes: 16 additions & 1 deletion src/emulator/emulatorLogger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import * as logger from "../logger";
* SUCCESS - useful to humans, similar to bullet but in a 'success' style.
* USER - logged by user code, always show to humans.
* WARN - warnings from our code that humans need.
* WARN_ONCE - warnings from our code that humans need, but only once per session.
*/
type LogType = "DEBUG" | "INFO" | "BULLET" | "SUCCESS" | "USER" | "WARN";
type LogType = "DEBUG" | "INFO" | "BULLET" | "SUCCESS" | "USER" | "WARN" | "WARN_ONCE";

const TYPE_VERBOSITY: { [type in LogType]: number } = {
DEBUG: 0,
Expand All @@ -17,6 +18,7 @@ const TYPE_VERBOSITY: { [type in LogType]: number } = {
SUCCESS: 1,
USER: 2,
WARN: 2,
WARN_ONCE: 2,
};

export enum Verbosity {
Expand All @@ -27,6 +29,7 @@ export enum Verbosity {

export class EmulatorLogger {
static verbosity: Verbosity = Verbosity.DEBUG;
static warnOnceCache = new Set<String>();

/**
* Within this file, utils.logFoo() or logger.Foo() should not be called directly,
Expand Down Expand Up @@ -54,6 +57,12 @@ export class EmulatorLogger {
case "WARN":
utils.logWarning(text);
break;
case "WARN_ONCE":
if (!this.warnOnceCache.has(text)) {
utils.logWarning(text);
this.warnOnceCache.add(text);
}
break;
case "SUCCESS":
utils.logSuccess(text);
break;
Expand All @@ -80,6 +89,12 @@ export class EmulatorLogger {
case "WARN":
utils.logLabeledWarning(label, text);
break;
case "WARN_ONCE":
if (!this.warnOnceCache.has(text)) {
utils.logLabeledWarning(label, text);
this.warnOnceCache.add(text);
}
break;
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/emulator/functionsEmulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,9 @@ You can probably fix this by running "npm install ${
case "WARN":
EmulatorLogger.logLabeled("WARN", "functions", log.text);
break;
case "WARN_ONCE":
EmulatorLogger.logLabeled("WARN_ONCE", "functions", log.text);
break;
case "FATAL":
EmulatorLogger.logLabeled("WARN", "functions", log.text);
break;
Expand Down
88 changes: 51 additions & 37 deletions src/emulator/functionsEmulatorRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
getEmulatedTriggersFromDefinitions,
getTemporarySocketPath,
} from "./functionsEmulatorShared";
import { parseVersionString, compareVersionStrings } from "./functionsEmulatorUtils";
import * as express from "express";
import * as path from "path";
import * as admin from "firebase-admin";
Expand Down Expand Up @@ -85,10 +86,11 @@ interface ModuleResolution {
resolution?: string;
}

interface ModuleVersion {
major: number;
minor: number;
patch: number;
interface SuccessfulModuleResolution {
declared: true;
installed: true;
version: string;
resolution: string;
}

interface ProxyTarget extends Object {
Expand Down Expand Up @@ -246,10 +248,26 @@ async function resolveDeveloperNodeModule(
return moduleResolution;
}

async function assertResolveDeveloperNodeModule(
frb: FunctionsRuntimeBundle,
name: string
): Promise<SuccessfulModuleResolution> {
const resolution = await resolveDeveloperNodeModule(frb, name);
if (
!(resolution.installed && resolution.declared && resolution.resolution && resolution.version)
) {
throw new Error(
`Assertion failure: could not fully resolve ${name}: ${JSON.stringify(resolution)}`
);
}

return resolution as SuccessfulModuleResolution;
}

async function verifyDeveloperNodeModules(frb: FunctionsRuntimeBundle): Promise<boolean> {
const modBundles = [
{ name: "firebase-admin", isDev: false, minVersion: 8 },
{ name: "firebase-functions", isDev: false, minVersion: 3 },
{ name: "firebase-admin", isDev: false, minVersion: "8.0.0" },
{ name: "firebase-functions", isDev: false, minVersion: "3.0.0" },
];

for (const modBundle of modBundles) {
Expand All @@ -268,8 +286,7 @@ async function verifyDeveloperNodeModules(frb: FunctionsRuntimeBundle): Promise<
return false;
}

const versionInfo = parseVersionString(resolution.version);
if (versionInfo.major < modBundle.minVersion) {
if (compareVersionStrings(resolution.version, modBundle.minVersion) < 0) {
new EmulatorLog("SYSTEM", "out-of-date-module", "", modBundle).log();
return false;
}
Expand Down Expand Up @@ -299,23 +316,6 @@ function requirePackageJson(frb: FunctionsRuntimeBundle): PackageJSON | undefine
}
}

/**
* Parse a semver version string into parts, filling in 0s where empty.
*/
function parseVersionString(version?: string): ModuleVersion {
const parts = (version || "0").split(".");

// Make sure "parts" always has 3 elements. Extras are ignored.
parts.push("0");
parts.push("0");

return {
major: parseInt(parts[0], 10),
minor: parseInt(parts[1], 10),
patch: parseInt(parts[2], 10),
};
}

/*
We mock out a ton of different paths that we can take to network I/O. It doesn't matter if they
overlap (like TLS and HTTPS) because the dev will either whitelist, block, or allow for one
Expand Down Expand Up @@ -433,21 +433,19 @@ function InitializeNetworkFiltering(frb: FunctionsRuntimeBundle): void {
https://github.com/firebase/firebase-functions/blob/9e3bda13565454543b4c7b2fd10fb627a6a3ab97/src/providers/https.ts#L66
*/
async function InitializeFirebaseFunctionsStubs(frb: FunctionsRuntimeBundle): Promise<void> {
const firebaseFunctionsResolution = await resolveDeveloperNodeModule(frb, "firebase-functions");
if (!firebaseFunctionsResolution.resolution) {
throw new Error("Could not resolve 'firebase-functions'");
}

const firebaseFunctionsResolution = await assertResolveDeveloperNodeModule(
frb,
"firebase-functions"
);
const firebaseFunctionsRoot = findModuleRoot(
"firebase-functions",
firebaseFunctionsResolution.resolution
);
const httpsProviderResolution = path.join(firebaseFunctionsRoot, "lib/providers/https");

// TODO: Remove this logic and stop relying on internal APIs. See #1480 for reasoning.
const functionsVersion = parseVersionString(firebaseFunctionsResolution.version!);
let methodName = "_onRequestWithOpts";
if (functionsVersion.major >= 3 && functionsVersion.minor >= 1) {
if (compareVersionStrings(firebaseFunctionsResolution.version, "3.1.0") >= 0) {
methodName = "_onRequestWithOptions";
}

Expand Down Expand Up @@ -514,12 +512,12 @@ function getDefaultConfig(): any {
* We also mock out firestore.settings() so we can merge the emulator settings with the developer's.
*/
async function InitializeFirebaseAdminStubs(frb: FunctionsRuntimeBundle): Promise<void> {
const adminResolution = await resolveDeveloperNodeModule(frb, "firebase-admin");
if (!adminResolution.resolution) {
throw new Error("Could not resolve 'firebase-admin'");
}
const adminResolution = await assertResolveDeveloperNodeModule(frb, "firebase-admin");
const localAdminModule = require(adminResolution.resolution);

const functionsResolution = await assertResolveDeveloperNodeModule(frb, "firebase-functions");
const localFunctionsModule = require(functionsResolution.resolution);

// Set up global proxied Firestore
proxiedFirestore = await makeProxiedFirestore(frb, localAdminModule);

Expand Down Expand Up @@ -558,6 +556,20 @@ async function InitializeFirebaseAdminStubs(frb: FunctionsRuntimeBundle): Promis
databaseApp = adminModuleTarget.initializeApp(databaseAppOptions, DATABASE_APP);
proxiedDatabase = makeProxiedDatabase(adminModuleTarget);

// Tell the Firebase Functions SDK to use the proxied app so that things like "change.after.ref"
// point to the right place.
if (localFunctionsModule.app.setEmulatedAdminApp) {
localFunctionsModule.app.setEmulatedAdminApp(defaultApp);
} else {
new EmulatorLog(
"WARN_ONCE",
"runtime-status",
`You're using firebase-functions v${
functionsResolution.version
}, please upgrade to firebase-functions v3.3.0 or higher for best results.`
).log();
}

return defaultApp;
})
.when("firestore", (target) => {
Expand Down Expand Up @@ -679,6 +691,7 @@ function warnAboutFirestoreProd(): void {
"runtime-status",
"The Cloud Firestore emulator is not running, so calls to Firestore will affect production."
).log();

hasAccessedFirestore = true;
}

Expand All @@ -688,10 +701,11 @@ function warnAboutDatabaseProd(): void {
}

new EmulatorLog(
"WARN",
"WARN_ONCE",
"runtime-status",
"The Realtime Database emulator is not running, so calls to Realtime Database will affect production."
).log();

hasAccessedDatabase = true;
}

Expand Down
59 changes: 56 additions & 3 deletions src/emulator/functionsEmulatorUtils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
/*
Please be careful when adding require/imports to this file, it is pulled into functionsEmulatorRuntime
which is ran in a separate node process, so it is likely to have unintended side-effects for you.
/**
* Please be careful when adding require/imports to this file, it is pulled into functionsEmulatorRuntime
* which is ran in a separate node process, so it is likely to have unintended side-effects for you.
*
* TODO(samstern): Merge this with functionsEmulatorShared
* TODO(samstern): Audit dependencies of functionsEmulatorShared
*/

const wildcardRegex = new RegExp("{[^/{}]*}");
const wildcardKeyRegex = new RegExp("^{(.+)}$");

export interface ModuleVersion {
major: number;
minor: number;
patch: number;
}

export function extractParamsFromPath(
wildcardPath: string,
snapshotPath: string
Expand Down Expand Up @@ -63,3 +72,47 @@ export function removePathSegments(path: string, count: number): string {
.slice(count)
.join("/");
}

/**
* Parse a semver version string into parts, filling in 0s where empty.
*/
export function parseVersionString(version?: string): ModuleVersion {
const parts = (version || "0").split(".");

// Make sure "parts" always has 3 elements. Extras are ignored.
parts.push("0");
parts.push("0");

return {
major: parseInt(parts[0], 10),
minor: parseInt(parts[1], 10),
patch: parseInt(parts[2], 10),
};
}

/**
* Compare two SemVer version strings.
*
* Returns:
* - Positive number if a is greater.
* - Negative number if b is greater.
* - Zero if they are the same.
*/
export function compareVersionStrings(a?: string, b?: string) {
const versionA = parseVersionString(a);
const versionB = parseVersionString(b);

if (versionA.major != versionB.major) {
return versionA.major - versionB.major;
}

if (versionA.minor != versionB.minor) {
return versionA.minor - versionB.minor;
}

if (versionA.patch != versionB.patch) {
return versionA.patch - versionB.patch;
}

return 0;
}

0 comments on commit e0e26fd

Please sign in to comment.