async function installExtension()

in src/commands/ext-install.ts [56:217]


async function installExtension(options: InstallExtensionOptions): Promise<void> {
  const { projectId, extensionName, source, extVersion, paramsEnvPath, nonInteractive, force } =
    options;
  const spec = source?.spec || extVersion?.spec;
  if (!spec) {
    throw new FirebaseError(
      `Could not find the extension.yaml for ${extensionName}. Please make sure this is a valid extension and try again.`
    );
  }
  const spinner = ora();
  try {
    await provisioningHelper.checkProductsProvisioned(projectId, spec);

    const usesSecrets = secretsUtils.usesSecrets(spec);
    if (spec.billingRequired || usesSecrets) {
      const enabled = await checkBillingEnabled(projectId);
      if (!enabled && nonInteractive) {
        throw new FirebaseError(
          `This extension requires the Blaze plan, but project ${projectId} is not on the Blaze plan. ` +
            marked(
              "Please visit https://console.cloud.google.com/billing/linkedaccount?project=${projectId} to upgrade your project."
            )
        );
      } else if (!enabled) {
        await displayNode10CreateBillingNotice(spec, false);
        await enableBilling(projectId);
      } else {
        await displayNode10CreateBillingNotice(spec, !nonInteractive);
      }
    }
    const apis = spec.apis || [];
    if (usesSecrets) {
      apis.push({
        apiName: "secretmanager.googleapis.com",
        reason: `To access and manage secrets which are used by this extension. By using this product you agree to the terms and conditions of the following license: https://console.cloud.google.com/tos?id=cloud&project=${projectId}`,
      });
    }
    if (apis.length) {
      askUserForConsent.displayApis(spec.displayName || spec.name, projectId, apis);
      const consented = await confirm({ nonInteractive, force, default: true });
      if (!consented) {
        throw new FirebaseError(
          "Without explicit consent for the APIs listed, we cannot deploy this extension."
        );
      }
    }
    if (usesSecrets) {
      await secretsUtils.ensureSecretManagerApiEnabled(options);
    }

    const roles = spec.roles ? spec.roles.map((role: extensionsApi.Role) => role.role) : [];
    if (roles.length) {
      await askUserForConsent.displayRoles(spec.displayName || spec.name, projectId, roles);
      const consented = await confirm({ nonInteractive, force, default: true });
      if (!consented) {
        throw new FirebaseError(
          "Without explicit consent for the roles listed, we cannot deploy this extension."
        );
      }
    }
    let instanceId = spec.name;

    let choice: "updateExisting" | "installNew" | "cancel";
    const anotherInstanceExists = await instanceIdExists(projectId, instanceId);
    if (anotherInstanceExists) {
      if (!nonInteractive) {
        choice = await promptForRepeatInstance(projectId, spec.name);
      } else if (nonInteractive && force) {
        choice = "updateExisting";
      } else {
        throw new FirebaseError(
          `An extension with the ID '${clc.bold(
            extensionName
          )}' already exists in the project '${clc.bold(projectId)}'.` +
            ` To update or reconfigure this instance instead, rerun this command with the --force flag.`
        );
      }
    } else {
      choice = "installNew";
    }
    let params: Record<string, string>;
    switch (choice) {
      case "installNew":
        instanceId = await promptForValidInstanceId(`${instanceId}-${getRandomString(4)}`);
        params = await paramHelper.getParams({
          projectId,
          paramSpecs: spec.params,
          nonInteractive,
          paramsEnvPath,
          instanceId,
        });
        spinner.text = "Installing your extension instance. This usually takes 3 to 5 minutes...";
        spinner.start();
        await extensionsApi.createInstance({
          projectId,
          instanceId,
          extensionSource: source,
          extensionVersionRef: extVersion?.ref,
          params,
        });
        spinner.stop();
        utils.logLabeledSuccess(
          logPrefix,
          `Successfully installed your instance of ${clc.bold(spec.displayName || spec.name)}! ` +
            `Its Instance ID is ${clc.bold(instanceId)}.`
        );
        break;
      case "updateExisting":
        params = await paramHelper.getParams({
          projectId,
          paramSpecs: spec.params,
          nonInteractive,
          paramsEnvPath,
          instanceId,
        });
        spinner.text = "Updating your extension instance. This usually takes 3 to 5 minutes...";
        spinner.start();
        await update({
          projectId,
          instanceId,
          source,
          extRef: extVersion?.ref,
          params,
        });
        spinner.stop();
        utils.logLabeledSuccess(
          logPrefix,
          `Successfully updated your instance of ${clc.bold(spec.displayName || spec.name)}! ` +
            `Its Instance ID is ${clc.bold(instanceId)}.`
        );
        break;
      case "cancel":
        return;
    }
    utils.logLabeledBullet(
      logPrefix,
      marked(
        "Go to the Firebase console to view instructions for using your extension, " +
          `which may include some required post-installation tasks: ${utils.consoleUrl(
            projectId,
            `/extensions/instances/${instanceId}?tab=usage`
          )}`
      )
    );
    logger.info(
      marked(
        "You can run `firebase ext` to view available Firebase Extensions commands, " +
          "including those to update, reconfigure, or delete your installed extension."
      )
    );
  } catch (err: any) {
    if (spinner.isSpinning) {
      spinner.fail();
    }
    if (err instanceof FirebaseError) {
      throw err;
    }
    throw new FirebaseError(`Error occurred installing extension: ${err.message}`, {
      original: err,
    });
  }
}