123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.ExtensionsEmulator = void 0;
- const clc = require("colorette");
- const spawn = require("cross-spawn");
- const fs = require("fs-extra");
- const os = require("os");
- const path = require("path");
- const Table = require("cli-table");
- const planner = require("../deploy/extensions/planner");
- const ensureApiEnabled_1 = require("../ensureApiEnabled");
- const error_1 = require("../error");
- const optionsHelper_1 = require("../extensions/emulator/optionsHelper");
- const refs_1 = require("../extensions/refs");
- const shortenUrl_1 = require("../shortenUrl");
- const constants_1 = require("./constants");
- const download_1 = require("./download");
- const emulatorLogger_1 = require("./emulatorLogger");
- const validation_1 = require("./extensions/validation");
- const registry_1 = require("./registry");
- const types_1 = require("./types");
- class ExtensionsEmulator {
- constructor(args) {
- this.want = [];
- this.backends = [];
- this.logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.EXTENSIONS);
- this.pendingDownloads = new Map();
- this.args = args;
- }
- start() {
- this.logger.logLabeled("DEBUG", "Extensions", "Started Extensions emulator, this is a noop.");
- return Promise.resolve();
- }
- stop() {
- this.logger.logLabeled("DEBUG", "Extensions", "Stopping Extensions emulator, this is a noop.");
- return Promise.resolve();
- }
- connect() {
- this.logger.logLabeled("DEBUG", "Extensions", "Connecting Extensions emulator, this is a noop.");
- return Promise.resolve();
- }
- getInfo() {
- const functionsEmulator = registry_1.EmulatorRegistry.get(types_1.Emulators.FUNCTIONS);
- if (!functionsEmulator) {
- throw new error_1.FirebaseError("Extensions Emulator is running but Functions emulator is not. This should never happen.");
- }
- return Object.assign(Object.assign({}, functionsEmulator.getInfo()), { name: this.getName() });
- }
- getName() {
- return types_1.Emulators.EXTENSIONS;
- }
- async readManifest() {
- var _a;
- this.want = await planner.want({
- projectId: this.args.projectId,
- projectNumber: this.args.projectNumber,
- aliases: (_a = this.args.aliases) !== null && _a !== void 0 ? _a : [],
- projectDir: this.args.projectDir,
- extensions: this.args.extensions,
- emulatorMode: true,
- });
- }
- async ensureSourceCode(instance) {
- if (instance.localPath) {
- if (!this.hasValidSource({ path: instance.localPath, extTarget: instance.localPath })) {
- throw new error_1.FirebaseError(`Tried to emulate local extension at ${instance.localPath}, but it was missing required files.`);
- }
- return path.resolve(instance.localPath);
- }
- else if (instance.ref) {
- const ref = (0, refs_1.toExtensionVersionRef)(instance.ref);
- const cacheDir = process.env.FIREBASE_EXTENSIONS_CACHE_PATH ||
- path.join(os.homedir(), ".cache", "firebase", "extensions");
- const sourceCodePath = path.join(cacheDir, ref);
- if (this.pendingDownloads.get(ref)) {
- await this.pendingDownloads.get(ref);
- }
- if (!this.hasValidSource({ path: sourceCodePath, extTarget: ref })) {
- const promise = this.downloadSource(instance, ref, sourceCodePath);
- this.pendingDownloads.set(ref, promise);
- await promise;
- }
- return sourceCodePath;
- }
- else {
- throw new error_1.FirebaseError("Tried to emulate an extension instance without a ref or localPath. This should never happen.");
- }
- }
- async downloadSource(instance, ref, sourceCodePath) {
- const extensionVersion = await planner.getExtensionVersion(instance);
- await (0, download_1.downloadExtensionVersion)(ref, extensionVersion.sourceDownloadUri, sourceCodePath);
- this.installAndBuildSourceCode(sourceCodePath);
- }
- hasValidSource(args) {
- const requiredFiles = [
- "./extension.yaml",
- "./functions/package.json",
- "./functions/node_modules",
- ];
- if (!fs.existsSync(args.path)) {
- return false;
- }
- for (const requiredFile of requiredFiles) {
- const f = path.join(args.path, requiredFile);
- if (!fs.existsSync(f)) {
- this.logger.logLabeled("BULLET", "extensions", `Detected invalid source code for ${args.extTarget}, expected to find ${f}`);
- return false;
- }
- }
- this.logger.logLabeled("DEBUG", "extensions", `Source code valid for ${args.extTarget}`);
- return true;
- }
- installAndBuildSourceCode(sourceCodePath) {
- this.logger.logLabeled("DEBUG", "Extensions", `Running "npm install" for ${sourceCodePath}`);
- const functionsDirectory = path.resolve(sourceCodePath, "functions");
- const npmInstall = spawn.sync("npm", ["install"], {
- encoding: "utf8",
- cwd: functionsDirectory,
- });
- if (npmInstall.error) {
- throw npmInstall.error;
- }
- this.logger.logLabeled("DEBUG", "Extensions", `Finished "npm install" for ${sourceCodePath}`);
- this.logger.logLabeled("DEBUG", "Extensions", `Running "npm run gcp-build" for ${sourceCodePath}`);
- const npmRunGCPBuild = spawn.sync("npm", ["run", "gcp-build"], {
- encoding: "utf8",
- cwd: functionsDirectory,
- });
- if (npmRunGCPBuild.error) {
- throw npmRunGCPBuild.error;
- }
- this.logger.logLabeled("DEBUG", "Extensions", `Finished "npm run gcp-build" for ${sourceCodePath}`);
- }
- async getExtensionBackends() {
- await this.readManifest();
- await this.checkAndWarnAPIs(this.want);
- this.backends = await Promise.all(this.want.map((i) => {
- return this.toEmulatableBackend(i);
- }));
- return this.backends;
- }
- async toEmulatableBackend(instance) {
- const extensionDir = await this.ensureSourceCode(instance);
- const functionsDir = path.join(extensionDir, "functions");
- const env = Object.assign(this.autoPopulatedParams(instance), instance.params);
- const { extensionTriggers, nodeMajorVersion, nonSecretEnv, secretEnvVariables } = await (0, optionsHelper_1.getExtensionFunctionInfo)(instance, env);
- const emulatableBackend = {
- functionsDir,
- env: nonSecretEnv,
- codebase: instance.instanceId,
- secretEnv: secretEnvVariables,
- predefinedTriggers: extensionTriggers,
- nodeMajorVersion: nodeMajorVersion,
- extensionInstanceId: instance.instanceId,
- };
- if (instance.ref) {
- emulatableBackend.extension = await planner.getExtension(instance);
- emulatableBackend.extensionVersion = await planner.getExtensionVersion(instance);
- }
- else if (instance.localPath) {
- emulatableBackend.extensionSpec = await planner.getExtensionSpec(instance);
- }
- return emulatableBackend;
- }
- autoPopulatedParams(instance) {
- var _a;
- const projectId = this.args.projectId;
- return {
- PROJECT_ID: projectId !== null && projectId !== void 0 ? projectId : "",
- EXT_INSTANCE_ID: instance.instanceId,
- DATABASE_INSTANCE: projectId !== null && projectId !== void 0 ? projectId : "",
- DATABASE_URL: `https://${projectId}.firebaseio.com`,
- STORAGE_BUCKET: `${projectId}.appspot.com`,
- ALLOWED_EVENT_TYPES: instance.allowedEventTypes ? instance.allowedEventTypes.join(",") : "",
- EVENTARC_CHANNEL: (_a = instance.eventarcChannel) !== null && _a !== void 0 ? _a : "",
- EVENTARC_CLOUD_EVENT_SOURCE: `projects/${projectId}/instances/${instance.instanceId}`,
- };
- }
- async checkAndWarnAPIs(instances) {
- const apisToWarn = await (0, validation_1.getUnemulatedAPIs)(this.args.projectId, instances);
- if (apisToWarn.length) {
- const table = new Table({
- head: [
- "API Name",
- "Instances using this API",
- `Enabled on ${this.args.projectId}`,
- `Enable this API`,
- ],
- style: { head: ["yellow"] },
- });
- for (const apiToWarn of apisToWarn) {
- const enablementUri = await (0, shortenUrl_1.shortenUrl)((0, ensureApiEnabled_1.enableApiURI)(this.args.projectId, apiToWarn.apiName));
- table.push([
- apiToWarn.apiName,
- apiToWarn.instanceIds,
- apiToWarn.enabled ? "Yes" : "No",
- apiToWarn.enabled ? "" : clc.bold(clc.underline(enablementUri)),
- ]);
- }
- if (constants_1.Constants.isDemoProject(this.args.projectId)) {
- this.logger.logLabeled("WARN", "Extensions", "The following Extensions make calls to Google Cloud APIs that do not have Emulators. " +
- `${clc.bold(this.args.projectId)} is a demo project, so these Extensions may not work as expected.\n` +
- table.toString());
- }
- else {
- this.logger.logLabeled("WARN", "Extensions", "The following Extensions make calls to Google Cloud APIs that do not have Emulators. " +
- `These calls will go to production Google Cloud APIs which may have real effects on ${clc.bold(this.args.projectId)}.\n` +
- table.toString());
- }
- }
- }
- filterUnemulatedTriggers(options, backends) {
- let foundUnemulatedTrigger = false;
- const filteredBackends = backends.filter((backend) => {
- const unemulatedServices = (0, validation_1.checkForUnemulatedTriggerTypes)(backend, options);
- if (unemulatedServices.length) {
- foundUnemulatedTrigger = true;
- const msg = ` ignored becuase it includes ${unemulatedServices.join(", ")} triggered functions, and the ${unemulatedServices.join(", ")} emulator does not exist or is not running.`;
- this.logger.logLabeled("WARN", `extensions[${backend.extensionInstanceId}]`, msg);
- }
- return unemulatedServices.length === 0;
- });
- if (foundUnemulatedTrigger) {
- const msg = "No Cloud Functions for these instances will be emulated, because partially emulating an Extension can lead to unexpected behavior. ";
- this.logger.log("WARN", msg);
- }
- return filteredBackends;
- }
- extensionDetailsUILink(backend) {
- if (!registry_1.EmulatorRegistry.isRunning(types_1.Emulators.UI) || !backend.extensionInstanceId) {
- return "";
- }
- const uiUrl = registry_1.EmulatorRegistry.url(types_1.Emulators.UI);
- uiUrl.pathname = `/${types_1.Emulators.EXTENSIONS}/${backend.extensionInstanceId}`;
- return clc.underline(clc.bold(uiUrl.toString()));
- }
- extensionsInfoTable(options) {
- var _a;
- const filtedBackends = this.filterUnemulatedTriggers(options, this.backends);
- const uiRunning = registry_1.EmulatorRegistry.isRunning(types_1.Emulators.UI);
- const tableHead = ["Extension Instance Name", "Extension Ref"];
- if (uiRunning) {
- tableHead.push("View in Emulator UI");
- }
- const table = new Table({ head: tableHead, style: { head: ["yellow"] } });
- for (const b of filtedBackends) {
- if (b.extensionInstanceId) {
- const tableEntry = [b.extensionInstanceId, ((_a = b.extensionVersion) === null || _a === void 0 ? void 0 : _a.ref) || "Local Extension"];
- if (uiRunning)
- tableEntry.push(this.extensionDetailsUILink(b));
- table.push(tableEntry);
- }
- }
- return table.toString();
- }
- }
- exports.ExtensionsEmulator = ExtensionsEmulator;
|