firebase-tools
Version:
Command-Line Interface for Firebase
205 lines (204 loc) • 9.36 kB
JavaScript
;
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(),
};
}
}