UNPKG

firebase-tools

Version:
234 lines (233 loc) • 11.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.command = void 0; const requireConfig_1 = require("../requireConfig"); const requireAuth_1 = require("../requireAuth"); const command_1 = require("../command"); const config_1 = require("../functions/kits/config"); const path_1 = require("path"); const error_1 = require("../error"); const prompt_1 = require("../prompt"); const posix_1 = require("path/posix"); const projectUtils_1 = require("../projectUtils"); const utils_1 = require("../utils"); const logger_1 = require("../logger"); const delete_1 = require("../deploy/functions/delete"); const ENV_FILE_PREFIX = ".env."; exports.command = new command_1.Command("functions:kits:uninstall") .description("uninstall a function kit or kit instance from your project. Deletes all running resources and the associated managed service account.") .before(requireConfig_1.requireConfig) .before(requireAuth_1.requireAuth) .option("--kit <kitId>", "") .option("--instance <instanceId>", "") .withForce() .action(async (options) => { const firebaseConfig = options.config; if (options.instance && options.kit) { throw new error_1.FirebaseError("Cannot specify both --kit and --instance. Please specify only one."); } if (!options.instance && !options.kit) { throw new error_1.FirebaseError("Must specify either --kit or --instance."); } if (typeof options.instance === "string") { await handleInstance(options, firebaseConfig); } if (typeof options.kit === "string") { await handleKit(options, firebaseConfig); } }); async function handleKit(options, config) { const kitId = options.kit; const kits = (0, config_1.listKitConfigs)(config.src); const kitConfig = kits.find((k) => k.kit === kitId); if (!kitConfig) { throw new error_1.FirebaseError(`Kit ${kitId} not found in firebase.json. Use functions:kits:list to get all existing kit IDs.`); } const conservativeDeletion = nonstandardKitLayout(kitConfig); logger_1.logger.debug(`Kit ${kitConfig.kit} uninstall mode: conservativeDeletion=${conservativeDeletion}`); if (Object.keys(kitConfig.instances).length > 0) { const willBlindDeleteMsg = conservativeDeletion ? "" : " and permanently remove their source code and configuration"; if (!(await (0, prompt_1.confirm)({ message: `Uninstall will delete all active instances of this kit${willBlindDeleteMsg}. You have the following active instances that will be deleted: ${Object.keys(kitConfig.instances).join(",")} Do you want to continue with uninstall (y/N)?`, default: false, nonInteractive: options.nonInteractive, force: options.force, }))) { return; } } await uninstallKit(options, config, kitConfig); return; } async function handleInstance(options, config) { const instanceId = options.instance; const projectId = (0, projectUtils_1.needProjectId)(options); const kits = (0, config_1.listKitConfigs)(config.src); const kitForInstance = kits.find((k) => instanceId in k.instances); if (typeof kitForInstance === "undefined") { throw new error_1.FirebaseError(`Instance ID ${instanceId} not found in firebase.json`); } const instanceConfigDirPath = kitForInstance.instances[instanceId]; const conservativeDeletion = nonstandardKitLayout(kitForInstance); logger_1.logger.debug(`Kit ${kitForInstance.kit} uninstall mode: conservativeDeletion=${conservativeDeletion}`); const envFileNames = getInstanceEnvNames(config, instanceConfigDirPath); const projectNamesInEnvs = envFileNames.map((envName) => options.rc ? options.rc.resolveAlias(envName) : envName); if (projectNamesInEnvs.length > 0 && !projectNamesInEnvs.includes(projectId)) { throw new error_1.FirebaseError(`Instance at ${instanceConfigDirPath} contains no .env file for current project`); } if (envFileNames.length === 0 || envFileNames.length === 1) { if (Object.keys(kitForInstance.instances).length > 1) { await uninstallInstance(options, config, instanceId, instanceConfigDirPath, conservativeDeletion); return; } const confirmMsg = `This is the last remaining instance for kit ${kitForInstance.kit}. This will delete all source and configuration code. Proceed? (y/N)`; if (!(await (0, prompt_1.confirm)({ message: confirmMsg, default: false, nonInteractive: options.nonInteractive, force: options.force, }))) { return; } await uninstallKit(options, config, kitForInstance); return; } let filesystemNameForProject = projectId; const envFilesWhichAliasToProject = envFileNames.filter((name) => (options.rc?.resolveAlias(name) || name) === projectId); if (envFilesWhichAliasToProject.length > 1) { throw new error_1.FirebaseError(`Instance ${instanceId} contains multiple .env files which ambiguously resolve to the current project ${envFilesWhichAliasToProject.map((s) => ENV_FILE_PREFIX + s).join(", ")}`); } if (envFilesWhichAliasToProject.length === 1) { filesystemNameForProject = envFilesWhichAliasToProject[0]; } await uninstallProjectInstance(options, config, filesystemNameForProject, instanceId, instanceConfigDirPath); } async function uninstallInstance(options, config, instanceId, instanceConfigDirPath, onlyDeleteEmpty) { await deleteInstancesFunctions(options, config, { [instanceId]: instanceConfigDirPath }); deleteInstanceConfigFiles(config, instanceConfigDirPath, onlyDeleteEmpty); let functionsConfig = config.src.functions ?? []; if (!Array.isArray(functionsConfig)) { functionsConfig = [functionsConfig]; } for (const stanza of functionsConfig) { if (typeof stanza.kit === "undefined") { continue; } const instances = stanza.instances ?? {}; for (const configInstanceId of Object.keys(instances)) { if (configInstanceId === instanceId) { delete stanza.instances[instanceId]; } } } config.set("functions", functionsConfig); config.writeProjectFile("firebase.json", config.src); } async function uninstallProjectInstance(options, config, envName, instanceId, kitInstancePath) { const projectId = options.rc?.resolveAlias(envName) || envName; const envFilePath = (0, path_1.join)(kitInstancePath, `${ENV_FILE_PREFIX}${envName}`); if (!config.projectFileExists(envFilePath)) { throw new error_1.FirebaseError(`Expected to clean up project kit instance .env file at ${envFilePath}, but it doesn't exist.`); } const epFilters = [{ codebase: instanceId }]; const deployContext = { projectId: projectId, filters: epFilters }; const deletionCount = await (0, delete_1.deleteFunctionsByEndpointFilters)(deployContext, options); if (deletionCount === 0) { logger_1.logger.info(`No deployed functions found for instance ${instanceId}. This is normal if firebase deploy was never run.`); } config.deleteProjectFile(envFilePath); } async function uninstallKit(options, config, kit) { const conservativeDeletion = nonstandardKitLayout(kit); await deleteInstancesFunctions(options, config, kit.instances); if (conservativeDeletion) { for (const instanceConfigDir of Object.values(kit.instances)) { deleteInstanceConfigFiles(config, instanceConfigDir); } const instanceConfigDirParents = Object.values(kit.instances).map((p) => (0, posix_1.dirname)(p)); if (instanceConfigDirParents.length > 0 && instanceConfigDirParents.every((p) => p === instanceConfigDirParents[0]) && configDirEmpty(config, instanceConfigDirParents[0])) { config.deleteProjectDir(instanceConfigDirParents[0]); } } else { const kitRootDirs = [ (0, posix_1.dirname)(kit.source), ...Object.values(kit.instances).map((p) => (0, posix_1.dirname)(p)), ]; if (!kitRootDirs.every((p) => p === kitRootDirs[0])) { throw new error_1.FirebaseError(`aborting kit uninstall: couldn't infer one kit root directory (${[...new Set(kitRootDirs)].join(", ")} in firebase.json)`); } config.deleteProjectDir(kitRootDirs[0]); } let functionsConfig = config.src.functions ?? []; if (!Array.isArray(functionsConfig)) { functionsConfig = [functionsConfig]; } functionsConfig = functionsConfig.filter((fc) => fc.kit !== kit.kit); config.set("functions", functionsConfig); config.writeProjectFile("firebase.json", config.src); } async function deleteInstancesFunctions(options, config, instances) { const instancesByProject = new Map(); for (const [instanceId, instanceConfigDir] of Object.entries(instances)) { for (const projectIdOrAlias of getInstanceEnvNames(config, instanceConfigDir)) { const projectId = options.rc ? options.rc.resolveAlias(projectIdOrAlias) : projectIdOrAlias; const list = instancesByProject.get(projectId) ?? []; list.push(instanceId); instancesByProject.set(projectId, list); } } for (const [projectId, instanceIds] of instancesByProject.entries()) { const epFilters = instanceIds.map((id) => ({ codebase: id })); const deployContext = { projectId, filters: epFilters }; const deletionCount = await (0, delete_1.deleteFunctionsByEndpointFilters)(deployContext, options); if (deletionCount === 0) { logger_1.logger.info(`No deployed functions found for instances: ${instanceIds.join(", ")}. This is normal if firebase deploy was never run.`); } } } function deleteInstanceConfigFiles(config, instanceConfigDirPath, onlyDeleteEmpty = true) { for (const projectIdOrAlias of getInstanceEnvNames(config, instanceConfigDirPath)) { config.deleteProjectFile((0, path_1.join)(instanceConfigDirPath, `${ENV_FILE_PREFIX}${projectIdOrAlias}`)); } if (!onlyDeleteEmpty || configDirEmpty(config, instanceConfigDirPath)) { config.deleteProjectDir(instanceConfigDirPath); } } function nonstandardKitLayout(kitConfig) { if (kitConfig.sourcePackage) { if (kitConfig.source !== `function-kits/${kitConfig.kit}/source`) { return true; } for (const [instanceId, instanceConfigDirPath] of Object.entries(kitConfig.instances)) { if (instanceConfigDirPath !== `function-kits/${kitConfig.kit}/config-${instanceId}`) { return true; } } return false; } return true; } function configDirEmpty(config, projectRelativePath) { const contents = config.lsProjectDir(projectRelativePath).map((dirent) => dirent.name); if (contents.length > 0) { const digest = contents.length > 5 ? `${contents.slice(0, 5).join(",")}, ...${contents.length - 5} more` : contents.join(","); (0, utils_1.logLabeledWarning)("functions", `Kits instance config directory ${projectRelativePath} still contains files (${digest}); not deleting automatically`); return false; } return true; } function getInstanceEnvNames(config, instanceConfigDirPath) { return config .lsProjectDir(instanceConfigDirPath) .filter((f) => f.isFile() && f.name.startsWith(ENV_FILE_PREFIX)) .map((f) => f.name.slice(ENV_FILE_PREFIX.length)); }