UNPKG

firebase-tools

Version:
205 lines (204 loc) 9.36 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.command = void 0; const checkMinRequiredVersion_1 = require("../checkMinRequiredVersion"); const command_1 = require("../command"); const planner = require("../deploy/extensions/planner"); const etags_1 = require("../extensions/etags"); const export_1 = require("../extensions/export"); const extensionsHelper_1 = require("../extensions/extensionsHelper"); const manifest = require("../extensions/manifest"); const paramHelper_1 = require("../extensions/paramHelper"); const functional_1 = require("../functional"); const getProjectNumber_1 = require("../getProjectNumber"); const logger_1 = require("../logger"); const projectUtils_1 = require("../projectUtils"); const prompt_1 = require("../prompt"); const requirePermissions_1 = require("../requirePermissions"); const extensionsApi_1 = require("../extensions/extensionsApi"); const utils_1 = require("../utils"); const env_1 = require("../functions/env"); const fs_1 = require("fs"); const path_1 = require("path"); const utils_2 = require("../utils"); const config_1 = require("../config"); const projectConfig_1 = require("../functions/projectConfig"); const error_1 = require("../error"); const clc = require("colorette"); const experiments = require("../experiments"); exports.command = new command_1.Command("ext:export") .description("export Extension instances installed on a project to a local Firebase directory") .option(`--mode <target>`, `experimental: controls the target system of the export (supports "extensions", "functions")`) .option(`-e`, `--extension-instance <instanceId>`, `scope the export to the single instance with the specified instance id`) .option(`-k`, "---kit-instance <kitId>", `write the .env export from --mode functions to the config path for a kit instance currently defined in firebase.json`) .option(`--outputDir <path>`, `override the .env export from --mode functions to a specific arbitrary path`) .before(requirePermissions_1.requirePermissions, ["firebaseextensions.instances.list"]) .before(extensionsHelper_1.ensureExtensionsApiEnabled) .before(checkMinRequiredVersion_1.checkMinRequiredVersion, "extMinVersion") .withForce() .action(async (options) => { if (experiments.isEnabled("extMigrationFeatures") && (options.mode === "functions" || options.mode === "kits")) { await fnHandler(options); } else { await extHandler(options); } }); async function extHandler(options) { const projectId = (0, projectUtils_1.needProjectId)(options); const projectNumber = await (0, getProjectNumber_1.getProjectNumber)(options); let have = await Promise.all(await planner.have(projectId)); if (have.length === 0) { logger_1.logger.info(`No extension instances installed on ${projectId}, so there is nothing to export.`); return; } if (options.extensionInstance) { have = have.filter((s) => s.instanceId === options.extensionInstance); if (have.length === 0) { logger_1.logger.info(`No extension instances installed on ${projectId} match specified instance ID ${options.extensionInstance}.`); return; } } const [withRef, withoutRef] = (0, functional_1.partition)(have, (s) => !!s.ref); const withRefSubbed = await Promise.all(withRef.map(async (i) => { const subbed = await (0, export_1.setSecretParamsToLatest)(i); return (0, export_1.parameterizeProject)(projectId, projectNumber, subbed); })); (0, export_1.displayExportInfo)(withRefSubbed, withoutRef); if (!options.nonInteractive && !options.force && !(await (0, prompt_1.confirm)({ message: "Do you wish to add these Extension instances to firebase.json?", default: true, }))) { logger_1.logger.info("Exiting. No changes made."); return; } const manifestSpecs = withRefSubbed.map((spec) => { const paramCopy = { ...spec.params, ...spec.systemParams }; if (spec.eventarcChannel) { paramCopy.EVENTARC_CHANNEL = spec.eventarcChannel; } if (spec.allowedEventTypes) { paramCopy.ALLOWED_EVENT_TYPES = spec.allowedEventTypes.join(","); } return { instanceId: spec.instanceId, ref: spec.ref, params: (0, paramHelper_1.buildBindingOptionsWithBaseValue)(paramCopy), }; }); const existingConfig = manifest.loadConfig(options); await manifest.writeToManifest(manifestSpecs, existingConfig, { nonInteractive: options.nonInteractive, force: options.force, }, true); (0, etags_1.saveEtags)(options.rc, projectId, have); } async function fnHandler(options) { if (!options.extensionInstance) { logger_1.logger.info(`ext:export must specify an --instance <instanceId> option when exporting to Functions. Use ext:list to find your instance IDs.`); return; } const projectId = (0, projectUtils_1.needProjectId)(options); const instance = await (0, extensionsApi_1.getInstance)(projectId, options.extensionInstance); if (typeof instance === "undefined") { logger_1.logger.info(`No extension matching instance ID ${options.extensionInstance} found`); return; } if (instance.state !== "ACTIVE" && !options.force) { throw new error_1.FirebaseError(`Extension ${options.extensionInstance} is in state ${instance.state}. To export a non-ACTIVE extension, use the --force option.`); } const instanceId = (0, utils_1.last)(instance.name.split("/")) ?? ""; if (instanceId !== options.extensionInstance) { return; } let secretCount = 0; const convertedEnv = (0, export_1.functionsEnvFromInstance)(instance); for (const key of Object.keys(convertedEnv)) { if (key.startsWith("FIREBASE_SECRET_REF_")) { secretCount += 1; } console.log(`${key}=${convertedEnv[key]}`); } const writeLocation = kitExportTarget(instanceId, projectId, options); if ((0, env_1.hasUserEnvs)(writeLocation)) { logger_1.logger.info(`Exported extensions config appears to already exist in /${instanceId}, aborting write.`); } else { (0, utils_2.logBullet)(clc.cyan(clc.bold("functions: ")) + `Saving exported extensions config as a Function Kits .env file`); (0, fs_1.mkdirSync)((0, path_1.join)(writeLocation.projectDir, writeLocation.configDir ?? instanceId), { recursive: true, }); (0, env_1.writeUserEnvs)(convertedEnv, writeLocation); } if (secretCount === 0) { return; } if (!(await (0, prompt_1.confirm)({ message: `${secretCount} Cloud Secret Manager resources found in export. Remove from Extensions lifecycle management?\nThis is necessary to prevent extension uninstall from deleting potentially migrated secrets.`, nonInteractive: options.nonInteractive, force: options.force, default: true, }))) { return; } const secretsChanged = await (0, export_1.ejectSecretsFromInstance)(instance); (0, utils_2.logBullet)(clc.cyan(clc.bold("functions: ")) + `Added functions-managed label to secrets: ${secretsChanged}`); } function kitExportTarget(instanceId, projectId, options) { const firebaseConfig = config_1.Config.load(options, true); if (typeof options.outputDir !== "undefined") { return { functionsSource: instanceId, configDir: String(options.outputDir), projectId: projectId, isEmulator: false, projectDir: options.cwd ?? process.cwd(), }; } if (typeof options.kitInstance !== "undefined") { if (!firebaseConfig) { throw new error_1.FirebaseError("--kit-instance option was provided but no firebase.json available to look in for kit definitions."); } const functionsConfig = firebaseConfig.src.functions ?? []; const validatedFunctions = (0, projectConfig_1.normalizeAndValidate)(functionsConfig); for (const fn of validatedFunctions) { if (!(0, projectConfig_1.isKitConfig)(fn)) { continue; } for (const [kitId, kitPath] of Object.entries(fn.instances)) { if (kitId === options.kitInstance) { return { functionsSource: instanceId, configDir: String(kitPath), projectId: projectId, isEmulator: false, projectDir: options.projectRoot ?? process.cwd(), }; } } } } if (typeof options.projectRoot !== "undefined") { return { functionsSource: instanceId, configDir: (0, path_1.join)(options.projectRoot, instanceId), projectId: projectId, isEmulator: false, projectDir: options.projectRoot, }; } else { return { functionsSource: instanceId, configDir: instanceId, projectId: projectId, isEmulator: false, projectDir: options.cwd ?? process.cwd(), }; } }