You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
370 lines
17 KiB
370 lines
17 KiB
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.JAVA_DEPRECATION_WARNING = exports.MIN_SUPPORTED_JAVA_MAJOR_VERSION = exports.checkJavaMajorVersion = exports.emulatorExec = exports.getListenOverview = exports.shutdownWhenKilled = exports.setExportOnExitOptions = exports.parseInspectionPort = exports.beforeEmulatorCommand = exports.warnEmulatorNotSupported = exports.printNoticeIfEmulated = exports.DESC_TEST_PARAMS = exports.FLAG_TEST_PARAMS = exports.DESC_TEST_CONFIG = exports.FLAG_TEST_CONFIG = exports.DESC_UI = exports.FLAG_UI = exports.EXPORT_ON_EXIT_CWD_DANGER = exports.EXPORT_ON_EXIT_USAGE_ERROR = exports.DESC_EXPORT_ON_EXIT = exports.FLAG_EXPORT_ON_EXIT = exports.FLAG_EXPORT_ON_EXIT_NAME = exports.DESC_IMPORT = exports.FLAG_IMPORT = exports.DESC_INSPECT_FUNCTIONS = exports.FLAG_INSPECT_FUNCTIONS = exports.DESC_ONLY = exports.FLAG_ONLY = void 0;
|
|
const clc = require("colorette");
|
|
const childProcess = require("child_process");
|
|
const controller = require("../emulator/controller");
|
|
const config_1 = require("../config");
|
|
const utils = require("../utils");
|
|
const logger_1 = require("../logger");
|
|
const path = require("path");
|
|
const constants_1 = require("./constants");
|
|
const requireAuth_1 = require("../requireAuth");
|
|
const requireConfig_1 = require("../requireConfig");
|
|
const types_1 = require("./types");
|
|
const error_1 = require("../error");
|
|
const registry_1 = require("./registry");
|
|
const projectUtils_1 = require("../projectUtils");
|
|
const prompt_1 = require("../prompt");
|
|
const fsutils = require("../fsutils");
|
|
const Table = require("cli-table");
|
|
const track_1 = require("../track");
|
|
const env_1 = require("./env");
|
|
exports.FLAG_ONLY = "--only <emulators>";
|
|
exports.DESC_ONLY = "only specific emulators. " +
|
|
"This is a comma separated list of emulator names. " +
|
|
"Valid options are: " +
|
|
JSON.stringify(types_1.ALL_SERVICE_EMULATORS);
|
|
exports.FLAG_INSPECT_FUNCTIONS = "--inspect-functions [port]";
|
|
exports.DESC_INSPECT_FUNCTIONS = "emulate Cloud Functions in debug mode with the node inspector on the given port (9229 if not specified)";
|
|
exports.FLAG_IMPORT = "--import [dir]";
|
|
exports.DESC_IMPORT = "import emulator data from a previous export (see emulators:export)";
|
|
exports.FLAG_EXPORT_ON_EXIT_NAME = "--export-on-exit";
|
|
exports.FLAG_EXPORT_ON_EXIT = `${exports.FLAG_EXPORT_ON_EXIT_NAME} [dir]`;
|
|
exports.DESC_EXPORT_ON_EXIT = "automatically export emulator data (emulators:export) " +
|
|
"when the emulators make a clean exit (SIGINT), " +
|
|
`when no dir is provided the location of ${exports.FLAG_IMPORT} is used`;
|
|
exports.EXPORT_ON_EXIT_USAGE_ERROR = `"${exports.FLAG_EXPORT_ON_EXIT_NAME}" must be used with "${exports.FLAG_IMPORT}"` +
|
|
` or provide a dir directly to "${exports.FLAG_EXPORT_ON_EXIT}"`;
|
|
exports.EXPORT_ON_EXIT_CWD_DANGER = `"${exports.FLAG_EXPORT_ON_EXIT_NAME}" must not point to the current directory or parents. Please choose a new/dedicated directory for exports.`;
|
|
exports.FLAG_UI = "--ui";
|
|
exports.DESC_UI = "run the Emulator UI";
|
|
exports.FLAG_TEST_CONFIG = "--test-config <firebase.json file>";
|
|
exports.DESC_TEST_CONFIG = "A firebase.json style file. Used to configure the Firestore and Realtime Database emulators.";
|
|
exports.FLAG_TEST_PARAMS = "--test-params <params.env file>";
|
|
exports.DESC_TEST_PARAMS = "A .env file containing test param values for your emulated extension.";
|
|
const DEFAULT_CONFIG = new config_1.Config({
|
|
eventarc: {},
|
|
database: {},
|
|
firestore: {},
|
|
functions: {},
|
|
hosting: {},
|
|
emulators: { auth: {}, pubsub: {} },
|
|
}, {});
|
|
function printNoticeIfEmulated(options, emulator) {
|
|
if (emulator !== types_1.Emulators.DATABASE && emulator !== types_1.Emulators.FIRESTORE) {
|
|
return;
|
|
}
|
|
const emuName = constants_1.Constants.description(emulator);
|
|
const envKey = emulator === types_1.Emulators.DATABASE
|
|
? constants_1.Constants.FIREBASE_DATABASE_EMULATOR_HOST
|
|
: constants_1.Constants.FIRESTORE_EMULATOR_HOST;
|
|
const envVal = process.env[envKey];
|
|
if (envVal) {
|
|
utils.logBullet(`You have set ${clc.bold(`${envKey}=${envVal}`)}, this command will execute against the ${emuName} running at that address.`);
|
|
}
|
|
}
|
|
exports.printNoticeIfEmulated = printNoticeIfEmulated;
|
|
function warnEmulatorNotSupported(options, emulator) {
|
|
if (emulator !== types_1.Emulators.DATABASE && emulator !== types_1.Emulators.FIRESTORE) {
|
|
return;
|
|
}
|
|
const emuName = constants_1.Constants.description(emulator);
|
|
const envKey = emulator === types_1.Emulators.DATABASE
|
|
? constants_1.Constants.FIREBASE_DATABASE_EMULATOR_HOST
|
|
: constants_1.Constants.FIRESTORE_EMULATOR_HOST;
|
|
const envVal = process.env[envKey];
|
|
if (envVal) {
|
|
utils.logWarning(`You have set ${clc.bold(`${envKey}=${envVal}`)}, however this command does not support running against the ${emuName} so this action will affect production.`);
|
|
const opts = {
|
|
confirm: undefined,
|
|
};
|
|
return (0, prompt_1.promptOnce)({
|
|
type: "confirm",
|
|
default: false,
|
|
message: "Do you want to continue?",
|
|
}).then(() => {
|
|
if (!opts.confirm) {
|
|
return utils.reject("Command aborted.", { exit: 1 });
|
|
}
|
|
});
|
|
}
|
|
}
|
|
exports.warnEmulatorNotSupported = warnEmulatorNotSupported;
|
|
async function beforeEmulatorCommand(options) {
|
|
const optionsWithDefaultConfig = Object.assign(Object.assign({}, options), { config: DEFAULT_CONFIG });
|
|
const optionsWithConfig = options.config ? options : optionsWithDefaultConfig;
|
|
const canStartWithoutConfig = options.only &&
|
|
!controller.shouldStart(optionsWithConfig, types_1.Emulators.FUNCTIONS) &&
|
|
!controller.shouldStart(optionsWithConfig, types_1.Emulators.HOSTING);
|
|
try {
|
|
await (0, requireAuth_1.requireAuth)(options);
|
|
}
|
|
catch (e) {
|
|
logger_1.logger.debug(e);
|
|
utils.logLabeledWarning("emulators", `You are not currently authenticated so some features may not work correctly. Please run ${clc.bold("firebase login")} to authenticate the CLI.`);
|
|
}
|
|
if (canStartWithoutConfig && !options.config) {
|
|
utils.logWarning("Could not find config (firebase.json) so using defaults.");
|
|
options.config = DEFAULT_CONFIG;
|
|
}
|
|
else {
|
|
await (0, requireConfig_1.requireConfig)(options);
|
|
}
|
|
}
|
|
exports.beforeEmulatorCommand = beforeEmulatorCommand;
|
|
function parseInspectionPort(options) {
|
|
let port = options.inspectFunctions;
|
|
if (port === true) {
|
|
port = "9229";
|
|
}
|
|
const parsed = Number(port);
|
|
if (isNaN(parsed) || parsed < 1024 || parsed > 65535) {
|
|
throw new error_1.FirebaseError(`"${port}" is not a valid port for debugging, please pass an integer between 1024 and 65535.`);
|
|
}
|
|
return parsed;
|
|
}
|
|
exports.parseInspectionPort = parseInspectionPort;
|
|
function setExportOnExitOptions(options) {
|
|
if (options.exportOnExit || typeof options.exportOnExit === "string") {
|
|
if (options.import) {
|
|
options.exportOnExit =
|
|
typeof options.exportOnExit === "string" ? options.exportOnExit : options.import;
|
|
const importPath = path.resolve(options.import);
|
|
if (!fsutils.dirExistsSync(importPath) && options.import === options.exportOnExit) {
|
|
options.exportOnExit = options.import;
|
|
delete options.import;
|
|
}
|
|
}
|
|
if (options.exportOnExit === true || !options.exportOnExit) {
|
|
throw new error_1.FirebaseError(exports.EXPORT_ON_EXIT_USAGE_ERROR);
|
|
}
|
|
if (path.resolve(".").startsWith(path.resolve(options.exportOnExit))) {
|
|
throw new error_1.FirebaseError(exports.EXPORT_ON_EXIT_CWD_DANGER);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
exports.setExportOnExitOptions = setExportOnExitOptions;
|
|
function processKillSignal(signal, res, rej, options) {
|
|
let lastSignal = new Date().getTime();
|
|
let signalCount = 0;
|
|
return async () => {
|
|
var _a;
|
|
try {
|
|
const now = new Date().getTime();
|
|
const diff = now - lastSignal;
|
|
if (diff < 100) {
|
|
logger_1.logger.debug(`Ignoring signal ${signal} due to short delay of ${diff}ms`);
|
|
return;
|
|
}
|
|
signalCount = signalCount + 1;
|
|
lastSignal = now;
|
|
const signalDisplay = signal === "SIGINT" ? `SIGINT (Ctrl-C)` : signal;
|
|
logger_1.logger.debug(`Received signal ${signalDisplay} ${signalCount}`);
|
|
logger_1.logger.info(" ");
|
|
if (signalCount === 1) {
|
|
utils.logLabeledBullet("emulators", `Received ${signalDisplay} for the first time. Starting a clean shutdown.`);
|
|
utils.logLabeledBullet("emulators", `Please wait for a clean shutdown or send the ${signalDisplay} signal again to stop right now.`);
|
|
await controller.onExit(options);
|
|
await controller.cleanShutdown();
|
|
}
|
|
else {
|
|
logger_1.logger.debug(`Skipping clean onExit() and cleanShutdown()`);
|
|
const runningEmulatorsInfosWithPid = registry_1.EmulatorRegistry.listRunningWithInfo().filter((i) => Boolean(i.pid));
|
|
utils.logLabeledWarning("emulators", `Received ${signalDisplay} ${signalCount} times. You have forced the Emulator Suite to exit without waiting for ${runningEmulatorsInfosWithPid.length} subprocess${runningEmulatorsInfosWithPid.length > 1 ? "es" : ""} to finish. These processes ${clc.bold("may")} still be running on your machine: `);
|
|
const pids = [];
|
|
const emulatorsTable = new Table({
|
|
head: ["Emulator", "Host:Port", "PID"],
|
|
style: {
|
|
head: ["yellow"],
|
|
},
|
|
});
|
|
for (const emulatorInfo of runningEmulatorsInfosWithPid) {
|
|
pids.push(emulatorInfo.pid);
|
|
emulatorsTable.push([
|
|
constants_1.Constants.description(emulatorInfo.name),
|
|
(_a = getListenOverview(emulatorInfo.name)) !== null && _a !== void 0 ? _a : "unknown",
|
|
emulatorInfo.pid,
|
|
]);
|
|
}
|
|
logger_1.logger.info(`\n${emulatorsTable}\n\nTo force them to exit run:\n`);
|
|
if (process.platform === "win32") {
|
|
logger_1.logger.info(clc.bold(`TASKKILL ${pids.map((pid) => "/PID " + pid).join(" ")} /T\n`));
|
|
}
|
|
else {
|
|
logger_1.logger.info(clc.bold(`kill ${pids.join(" ")}\n`));
|
|
}
|
|
}
|
|
res();
|
|
}
|
|
catch (e) {
|
|
logger_1.logger.debug(e);
|
|
rej();
|
|
}
|
|
};
|
|
}
|
|
function shutdownWhenKilled(options) {
|
|
return new Promise((res, rej) => {
|
|
["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"].forEach((signal) => {
|
|
process.on(signal, processKillSignal(signal, res, rej, options));
|
|
});
|
|
}).catch((e) => {
|
|
logger_1.logger.debug(e);
|
|
utils.logLabeledWarning("emulators", "emulators failed to shut down cleanly, see firebase-debug.log for details.");
|
|
throw e;
|
|
});
|
|
}
|
|
exports.shutdownWhenKilled = shutdownWhenKilled;
|
|
async function runScript(script, extraEnv) {
|
|
utils.logBullet(`Running script: ${clc.bold(script)}`);
|
|
const env = Object.assign(Object.assign({}, process.env), extraEnv);
|
|
const emulatorInfos = registry_1.EmulatorRegistry.listRunningWithInfo();
|
|
(0, env_1.setEnvVarsForEmulators)(env, emulatorInfos);
|
|
const proc = childProcess.spawn(script, {
|
|
stdio: ["inherit", "inherit", "inherit"],
|
|
shell: true,
|
|
windowsHide: true,
|
|
env,
|
|
});
|
|
logger_1.logger.debug(`Running ${script} with environment ${JSON.stringify(env)}`);
|
|
return new Promise((resolve, reject) => {
|
|
proc.on("error", (err) => {
|
|
utils.logWarning(`There was an error running the script: ${JSON.stringify(err)}`);
|
|
reject();
|
|
});
|
|
const exitDelayMs = 500;
|
|
proc.once("exit", (code, signal) => {
|
|
if (signal) {
|
|
utils.logWarning(`Script exited with signal: ${signal}`);
|
|
setTimeout(reject, exitDelayMs);
|
|
return;
|
|
}
|
|
const exitCode = code || 0;
|
|
if (code === 0) {
|
|
utils.logSuccess(`Script exited successfully (code 0)`);
|
|
}
|
|
else {
|
|
utils.logWarning(`Script exited unsuccessfully (code ${code})`);
|
|
}
|
|
setTimeout(() => {
|
|
resolve(exitCode);
|
|
}, exitDelayMs);
|
|
});
|
|
});
|
|
}
|
|
function getListenOverview(emulator) {
|
|
var _a;
|
|
const info = (_a = registry_1.EmulatorRegistry.get(emulator)) === null || _a === void 0 ? void 0 : _a.getInfo();
|
|
if (!info) {
|
|
return undefined;
|
|
}
|
|
if (info.host.includes(":")) {
|
|
return `[${info.host}]:${info.port}`;
|
|
}
|
|
else {
|
|
return `${info.host}:${info.port}`;
|
|
}
|
|
}
|
|
exports.getListenOverview = getListenOverview;
|
|
async function emulatorExec(script, options) {
|
|
const projectId = (0, projectUtils_1.getProjectId)(options);
|
|
const extraEnv = {};
|
|
if (projectId) {
|
|
extraEnv.GCLOUD_PROJECT = projectId;
|
|
}
|
|
const session = (0, track_1.emulatorSession)();
|
|
if (session && session.debugMode) {
|
|
extraEnv[constants_1.Constants.FIREBASE_GA_SESSION] = JSON.stringify(session);
|
|
}
|
|
let exitCode = 0;
|
|
let deprecationNotices;
|
|
try {
|
|
const showUI = !!options.ui;
|
|
({ deprecationNotices } = await controller.startAll(options, showUI));
|
|
exitCode = await runScript(script, extraEnv);
|
|
await controller.onExit(options);
|
|
}
|
|
finally {
|
|
await controller.cleanShutdown();
|
|
}
|
|
for (const notice of deprecationNotices) {
|
|
utils.logLabeledWarning("emulators", notice, "warn");
|
|
}
|
|
if (exitCode !== 0) {
|
|
throw new error_1.FirebaseError(`Script "${clc.bold(script)}" exited with code ${exitCode}`, {
|
|
exit: exitCode,
|
|
});
|
|
}
|
|
}
|
|
exports.emulatorExec = emulatorExec;
|
|
const JAVA_VERSION_REGEX = /version "([1-9][0-9]*)/;
|
|
const JAVA_HINT = "Please make sure Java is installed and on your system PATH.";
|
|
async function checkJavaMajorVersion() {
|
|
return new Promise((resolve, reject) => {
|
|
var _a, _b;
|
|
let child;
|
|
try {
|
|
child = childProcess.spawn("java", ["-Duser.language=en", "-Dfile.encoding=UTF-8", "-version"], {
|
|
stdio: ["inherit", "pipe", "pipe"],
|
|
});
|
|
}
|
|
catch (err) {
|
|
return reject(new error_1.FirebaseError(`Could not spawn \`java -version\`. ${JAVA_HINT}`, { original: err }));
|
|
}
|
|
let output = "";
|
|
let error = "";
|
|
(_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on("data", (data) => {
|
|
const str = data.toString("utf8");
|
|
logger_1.logger.debug(str);
|
|
output += str;
|
|
});
|
|
(_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on("data", (data) => {
|
|
const str = data.toString("utf8");
|
|
logger_1.logger.debug(str);
|
|
error += str;
|
|
});
|
|
child.once("error", (err) => {
|
|
reject(new error_1.FirebaseError(`Could not spawn \`java -version\`. ${JAVA_HINT}`, { original: err }));
|
|
});
|
|
child.once("exit", (code, signal) => {
|
|
if (signal) {
|
|
reject(new error_1.FirebaseError(`Process \`java -version\` was killed by signal ${signal}.`));
|
|
}
|
|
else if (code && code !== 0) {
|
|
reject(new error_1.FirebaseError(`Process \`java -version\` has exited with code ${code}. ${JAVA_HINT}\n` +
|
|
`-----Original stdout-----\n${output}` +
|
|
`-----Original stderr-----\n${error}`));
|
|
}
|
|
else {
|
|
resolve(`${output}\n${error}`);
|
|
}
|
|
});
|
|
}).then((output) => {
|
|
let versionInt = -1;
|
|
const match = output.match(JAVA_VERSION_REGEX);
|
|
if (match) {
|
|
const version = match[1];
|
|
versionInt = parseInt(version, 10);
|
|
if (!versionInt) {
|
|
utils.logLabeledWarning("emulators", `Failed to parse Java version. Got "${match[0]}".`, "warn");
|
|
}
|
|
else {
|
|
logger_1.logger.debug(`Parsed Java major version: ${versionInt}`);
|
|
}
|
|
}
|
|
else {
|
|
logger_1.logger.debug("java -version outputs:", output);
|
|
logger_1.logger.warn(`Failed to parse Java version.`);
|
|
}
|
|
const session = (0, track_1.emulatorSession)();
|
|
if (session) {
|
|
session.javaMajorVersion = versionInt;
|
|
}
|
|
return versionInt;
|
|
});
|
|
}
|
|
exports.checkJavaMajorVersion = checkJavaMajorVersion;
|
|
exports.MIN_SUPPORTED_JAVA_MAJOR_VERSION = 11;
|
|
exports.JAVA_DEPRECATION_WARNING = "firebase-tools no longer supports Java version before 11. " +
|
|
"Please upgrade to Java version 11 or above to continue using the emulators.";
|