text: "use strict";
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
      if (k2 === undefined) k2 = k;
      Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
  }) : (function(o, m, k, k2) {
      if (k2 === undefined) k2 = k;
      o[k2] = m[k];
  }));
  var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
      Object.defineProperty(o, "default", { enumerable: true, value: v });
  }) : function(o, v) {
      o["default"] = v;
  });
  var __importStar = (this && this.__importStar) || function (mod) {
      if (mod && mod.__esModule) return mod;
      var result = {};
      if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
      __setModuleDefault(result, mod);
      return result;
  };
  var __importDefault = (this && this.__importDefault) || function (mod) {
      return (mod && mod.__esModule) ? mod : { "default": mod };
  };
  Object.defineProperty(exports, "__esModule", { value: true });
  exports.generateAndUploadRootStack = exports.uploadTemplateToS3 = exports.storeCurrentCloudBackend = exports.updateStackForAPIMigration = exports.run = void 0;
  const lodash_1 = __importDefault(require("lodash"));
  const fs = __importStar(require("fs-extra"));
  const os_1 = require("os");
  const path = __importStar(require("path"));
  const cfn_lint_1 = require("cfn-lint");
  const glob_1 = __importDefault(require("glob"));
  const amplify_cli_core_1 = require("amplify-cli-core");
  const ora_1 = __importDefault(require("ora"));
  const aws_s3_1 = require("./aws-utils/aws-s3");
  const aws_cfn_1 = __importDefault(require("./aws-utils/aws-cfn"));
  const user_agent_1 = require("./aws-utils/user-agent");
  const constants_1 = __importStar(require("./constants"));
  const upload_appsync_files_1 = require("./upload-appsync-files");
  const graphql_codegen_1 = require("./graphql-codegen");
  const admin_modelgen_1 = require("./admin-modelgen");
  const auth_transform_1 = require("./auth-transform");
  const transform_graphql_schema_1 = require("./transform-graphql-schema");
  const display_helpful_urls_1 = require("./display-helpful-urls");
  const download_api_models_1 = require("./download-api-models");
  const graphql_transformer_1 = require("./graphql-transformer");
  const resourceParams_1 = require("./resourceParams");
  const upload_auth_trigger_files_1 = require("./upload-auth-trigger-files");
  const archiver_1 = __importDefault(require("./utils/archiver"));
  const amplify_service_manager_1 = __importDefault(require("./amplify-service-manager"));
  const iterative_deployment_1 = require("./iterative-deployment");
  const cloudform_types_1 = require("cloudform-types");
  const utils_1 = require("./graphql-transformer/utils");
  const admin_helpers_1 = require("./utils/admin-helpers");
  const aws_logger_1 = require("./utils/aws-logger");
  const consolidate_apigw_policies_1 = require("./utils/consolidate-apigw-policies");
  const env_level_constructs_1 = require("./utils/env-level-constructs");
  const stack_1 = require("./network/stack");
  const cfn_pre_processor_1 = require("./pre-push-cfn-processor/cfn-pre-processor");
  const upload_auth_trigger_template_1 = require("./utils/upload-auth-trigger-template");
  const remove_dependent_function_1 = require("./utils/remove-dependent-function");
  const logger = aws_logger_1.fileLogger('push-resources');
  const FunctionServiceNameLambdaLayer = 'LambdaLayer';
  const ApiServiceNameElasticContainer = 'ElasticContainer';
  const spinner = ora_1.default('Updating resources in the cloud. This may take a few minutes...');
  const rootStackFileName = 'rootStackTemplate.json';
  const nestedStackFileName = 'nested-cloudformation-stack.yml';
  const optionalBuildDirectoryName = 'build';
  const cfnTemplateGlobPattern = '*template*.+(yaml|yml|json)';
  const parametersJson = 'parameters.json';
  const deploymentInProgressErrorMessage = (context) => {
      context.print.error('A deployment is in progress.');
      context.print.error('If the prior rollback was aborted, run:');
      context.print.error('"amplify push --iterative-rollback" to rollback the prior deployment');
      context.print.error('"amplify push --force" to re-deploy');
  };
  async function run(context, resourceDefinition) {
      var _a, _b, _c, _d;
      const deploymentStateManager = await iterative_deployment_1.DeploymentStateManager.createDeploymentStateManager(context);
      let iterativeDeploymentWasInvoked = false;
      try {
          const { resourcesToBeCreated, resourcesToBeUpdated, resourcesToBeSynced, resourcesToBeDeleted, tagsUpdated, allResources, } = resourceDefinition;
          const cloudformationMeta = context.amplify.getProjectMeta().providers.awscloudformation;
          const { parameters: { options }, } = context;
          let resources = !!((_a = context === null || context === void 0 ? void 0 : context.exeInfo) === null || _a === void 0 ? void 0 : _a.forcePush) ? allResources : resourcesToBeCreated.concat(resourcesToBeUpdated);
          if (deploymentStateManager.isDeploymentInProgress() && !deploymentStateManager.isDeploymentFinished()) {
              if (((_b = context.exeInfo) === null || _b === void 0 ? void 0 : _b.forcePush) || ((_c = context.exeInfo) === null || _c === void 0 ? void 0 : _c.iterativeRollback)) {
                  await iterative_deployment_1.runIterativeRollback(context, cloudformationMeta, deploymentStateManager);
                  if ((_d = context.exeInfo) === null || _d === void 0 ? void 0 : _d.iterativeRollback) {
                      return;
                  }
              }
          }
          await env_level_constructs_1.createEnvLevelConstructs(context);
          const apiResourceTobeUpdated = resourcesToBeUpdated.filter(resource => resource.service === 'AppSync');
          if (apiResourceTobeUpdated.length) {
              const functionResourceToBeUpdated = await remove_dependent_function_1.ensureValidFunctionModelDependencies(context, apiResourceTobeUpdated, allResources);
              if (functionResourceToBeUpdated !== undefined && functionResourceToBeUpdated.length > 0) {
                  resources = lodash_1.default.uniqBy(resources.concat(functionResourceToBeUpdated), `resourceName`);
              }
          }
          validateCfnTemplates(context, resources);
          for await (const resource of resources) {
              if (resource.service === ApiServiceNameElasticContainer && resource.category === 'api') {
                  const { exposedContainer, pipelineInfo: { consoleUrl }, } = await context.amplify.invokePluginMethod(context, 'api', undefined, 'generateContainersArtifacts', [context, resource]);
                  await context.amplify.updateamplifyMetaAfterResourceUpdate('api', resource.resourceName, 'exposedContainer', exposedContainer);
                  context.print.info(`\nIn a few moments, you can check image build status for ${resource.resourceName} at the following URL:`);
                  context.print.info(`${consoleUrl}\n`);
                  context.print.info(`It may take a few moments for this to appear. If you have trouble with first time deployments, please try refreshing this page after a few moments and watch the CodeBuild Details for debugging information.`);
                  if (resourcesToBeUpdated.find(res => res.resourceName === resource.resourceName)) {
                      resource.lastPackageTimeStamp = undefined;
                      await context.amplify.updateamplifyMetaAfterResourceUpdate('api', resource.resourceName, 'lastPackageTimeStamp', undefined);
                  }
              }
              if (resource.service === ApiServiceNameElasticContainer && resource.category === 'hosting') {
                  await context.amplify.invokePluginMethod(context, 'hosting', 'ElasticContainer', 'generateHostingResources', [context, resource]);
              }
          }
          await prepareBuildableResources(context, resources);
          await transform_graphql_schema_1.transformGraphQLSchema(context, {
              handleMigration: opts => updateStackForAPIMigration(context, 'api', undefined, opts),
              minify: options['minify'],
          });
          if (deploymentStateManager.isDeploymentInProgress()) {
              deploymentInProgressErrorMessage(context);
              return;
          }
          let deploymentSteps = [];
          let stateFolder = {};
          if (amplify_cli_core_1.FeatureFlags.getBoolean('graphQLTransformer.enableIterativeGSIUpdates')) {
              const gqlResource = utils_1.getGqlUpdatedResource(resourcesToBeUpdated);
              if (gqlResource) {
                  const gqlManager = await graphql_transformer_1.GraphQLResourceManager.createInstance(context, gqlResource, cloudformationMeta.StackId);
                  deploymentSteps = await gqlManager.run();
                  if (deploymentSteps.length > 1) {
                      iterativeDeploymentWasInvoked = true;
                      const deploymentStepStates = new Array(deploymentSteps.length + 1).fill(true).map(() => ({
                          status: amplify_cli_core_1.DeploymentStepStatus.WAITING_FOR_DEPLOYMENT,
                      }));
                      if (!(await deploymentStateManager.startDeployment(deploymentStepStates))) {
                          deploymentInProgressErrorMessage(context);
                          return;
                      }
                  }
                  stateFolder.local = gqlManager.getStateFilesDirectory();
                  stateFolder.cloud = await gqlManager.getCloudStateFilesDirectory();
              }
          }
          await upload_appsync_files_1.uploadAppSyncFiles(context, resources, allResources);
          await auth_transform_1.prePushAuthTransform(context, resources);
          await graphql_codegen_1.prePushGraphQLCodegen(context, resourcesToBeCreated, resourcesToBeUpdated);
          let projectDetails = context.amplify.getProjectDetails();
          await updateS3Templates(context, resources, projectDetails.amplifyMeta);
          if (resourcesToBeCreated.length > 0 || resourcesToBeUpdated.length > 0 || resourcesToBeDeleted.length > 0 || tagsUpdated) {
              if (deploymentSteps.length > 1) {
                  const deploymentManager = await iterative_deployment_1.DeploymentManager.createInstance(context, cloudformationMeta.DeploymentBucketName, spinner, {
                      userAgent: user_agent_1.formUserAgentParam(context, generateUserAgentAction(resourcesToBeCreated, resourcesToBeUpdated)),
                  });
                  deploymentSteps.forEach(step => deploymentManager.addStep(step));
                  const backEndDir = amplify_cli_core_1.pathManager.getBackendDirPath();
                  const nestedStackFilepath = path.normalize(path.join(backEndDir, constants_1.ProviderName, nestedStackFileName));
                  await generateAndUploadRootStack(context, nestedStackFilepath, nestedStackFileName);
                  const finalStep = {
                      stackTemplatePathOrUrl: nestedStackFileName,
                      tableNames: [],
                      stackName: cloudformationMeta.StackName,
                      parameters: {
                          DeploymentBucketName: cloudformationMeta.DeploymentBucketName,
                          AuthRoleName: cloudformationMeta.AuthRoleName,
                          UnauthRoleName: cloudformationMeta.UnauthRoleName,
                      },
                      capabilities: ['CAPABILITY_NAMED_IAM', 'CAPABILITY_AUTO_EXPAND'],
                  };
                  deploymentManager.addStep({
                      deployment: finalStep,
                      rollback: deploymentSteps[deploymentSteps.length - 1].deployment,
                  });
                  spinner.start();
                  await deploymentManager.deploy(deploymentStateManager);
                  if (stateFolder.local) {
                      try {
                          fs.removeSync(stateFolder.local);
                      }
                      catch (err) {
                          context.print.error(`Could not delete state directory locally: ${err}`);
                      }
                  }
                  if (stateFolder.cloud) {
                      const s3 = await aws_s3_1.S3.getInstance(context);
                      await s3.deleteDirectory(cloudformationMeta.DeploymentBucketName, stateFolder.cloud);
                  }
              }
              else {
                  spinner.start();
                  const nestedStack = await formNestedStack(context, context.amplify.getProjectDetails());
                  await updateCloudFormationNestedStack(context, nestedStack, resourcesToBeCreated, resourcesToBeUpdated);
              }
          }
          await graphql_codegen_1.postPushGraphQLCodegen(context);
          await amplify_service_manager_1.default.postPushCheck(context);
          if (resources.concat(resourcesToBeDeleted).length > 0) {
              await context.amplify.updateamplifyMetaAfterPush(resources);
          }
          if (resourcesToBeSynced.length > 0) {
              const importResources = resourcesToBeSynced.filter((r) => r.sync === 'import');
              const unlinkedResources = resourcesToBeSynced.filter((r) => r.sync === 'unlink');
              if (importResources.length > 0) {
                  await context.amplify.updateamplifyMetaAfterPush(importResources);
              }
              if (unlinkedResources.length > 0) {
                  for (let i = 0; i < unlinkedResources.length; i++) {
                      context.amplify.updateamplifyMetaAfterResourceDelete(unlinkedResources[i].category, unlinkedResources[i].resourceName);
                  }
              }
          }
          for (let i = 0; i < resourcesToBeDeleted.length; i++) {
              context.amplify.updateamplifyMetaAfterResourceDelete(resourcesToBeDeleted[i].category, resourcesToBeDeleted[i].resourceName);
          }
          await upload_auth_trigger_files_1.uploadAuthTriggerFiles(context, resourcesToBeCreated, resourcesToBeUpdated);
          let updatedAllResources = (await context.amplify.getResourceStatus()).allResources;
          const newAPIresources = [];
          updatedAllResources = updatedAllResources.filter((resource) => resource.service === 'API Gateway');
          for (let i = 0; i < updatedAllResources.length; i++) {
              if (resources.findIndex(resource => resource.resourceName === updatedAllResources[i].resourceName) > -1) {
                  newAPIresources.push(updatedAllResources[i]);
              }
          }
          if (resourcesToBeSynced.length > 0) {
              const importResources = resourcesToBeSynced.filter((r) => r.sync === 'import');
              if (importResources.length > 0) {
                  const { imported, userPoolId } = context.amplify.getImportedAuthProperties(context);
                  if (imported) {
                      const appSyncAPIs = allResources.filter((resource) => resource.service === 'AppSync');
                      const meta = amplify_cli_core_1.stateManager.getMeta(undefined);
                      let hasChanges = false;
                      for (const appSyncAPI of appSyncAPIs) {
                          const apiResource = lodash_1.default.get(meta, ['api', appSyncAPI.resourceName]);
                          if (apiResource) {
                              const defaultAuthentication = lodash_1.default.get(apiResource, ['output', 'authConfig', 'defaultAuthentication']);
                              if (defaultAuthentication && defaultAuthentication.authenticationType === 'AMAZON_COGNITO_USER_POOLS') {
                                  defaultAuthentication.userPoolConfig.userPoolId = userPoolId;
                                  hasChanges = true;
                              }
                              const additionalAuthenticationProviders = lodash_1.default.get(apiResource, ['output', 'authConfig', 'additionalAuthenticationProviders']);
                              for (const additionalAuthenticationProvider of additionalAuthenticationProviders) {
                                  if (additionalAuthenticationProvider &&
                                      additionalAuthenticationProvider.authenticationType === 'AMAZON_COGNITO_USER_POOLS') {
                                      additionalAuthenticationProvider.userPoolConfig.userPoolId = userPoolId;
                                      hasChanges = true;
                                  }
                              }
                          }
                      }
                      if (hasChanges) {
                          amplify_cli_core_1.stateManager.setMeta(undefined, meta);
                      }
                  }
              }
          }
          await download_api_models_1.downloadAPIModels(context, newAPIresources);
          await storeCurrentCloudBackend(context);
          await amplify_service_manager_1.default.storeArtifactsForAmplifyService(context);
          resources
              .filter(resource => resource.category === 'auth' && resource.service === 'Cognito' && resource.providerPlugin === 'awscloudformation')
              .map(({ category, resourceName }) => context.amplify.removeDeploymentSecrets(context, category, resourceName));
          await admin_modelgen_1.adminModelgen(context, resources);
          spinner.succeed('All resources are updated in the cloud');
          await display_helpful_urls_1.displayHelpfulURLs(context, resources);
      }
      catch (error) {
          if (iterativeDeploymentWasInvoked) {
              await deploymentStateManager.failDeployment();
          }
          spinner.fail('An error occurred when pushing the resources to the cloud');
          logger('run', [resourceDefinition])(error);
          throw error;
      }
  }
  exports.run = run;
  async function updateStackForAPIMigration(context, category, resourceName, options) {
      const { resourcesToBeCreated, resourcesToBeUpdated, resourcesToBeDeleted, allResources } = await context.amplify.getResourceStatus(category, resourceName, constants_1.ProviderName);
      const { isReverting, isCLIMigration } = options;
      let resources = resourcesToBeCreated.concat(resourcesToBeUpdated);
      let projectDetails = context.amplify.getProjectDetails();
      validateCfnTemplates(context, resources);
      resources = allResources.filter(resource => resource.service === 'AppSync');
      await upload_appsync_files_1.uploadAppSyncFiles(context, resources, allResources, {
          useDeprecatedParameters: isReverting,
          defaultParams: {
              CreateAPIKey: 0,
              APIKeyExpirationEpoch: -1,
              authRoleName: {
                  Ref: 'AuthRoleName',
              },
              unauthRoleName: {
                  Ref: 'UnauthRoleName',
              },
          },
      });
      await updateS3Templates(context, resources, projectDetails.amplifyMeta);
      try {
          if (!isCLIMigration) {
              spinner.start();
          }
          projectDetails = context.amplify.getProjectDetails();
          if (resources.length > 0 || resourcesToBeDeleted.length > 0) {
              let nestedStack;
              if (isReverting && isCLIMigration) {
                  nestedStack = await formNestedStack(context, projectDetails, category, resourceName, 'AppSync', true);
              }
              else if (isCLIMigration) {
                  nestedStack = await formNestedStack(context, projectDetails, category, resourceName, 'AppSync');
              }
              else {
                  nestedStack = await formNestedStack(context, projectDetails, category);
              }
              await updateCloudFormationNestedStack(context, nestedStack, resourcesToBeCreated, resourcesToBeUpdated);
          }
          await context.amplify.updateamplifyMetaAfterPush(resources);
          if (!isCLIMigration) {
              spinner.stop();
          }
      }
      catch (error) {
          if (!isCLIMigration) {
              spinner.fail('An error occurred when migrating the API project.');
          }
          throw error;
      }
  }
  exports.updateStackForAPIMigration = updateStackForAPIMigration;
  async function storeCurrentCloudBackend(context) {
      const zipFilename = '#current-cloud-backend.zip';
      const backendDir = amplify_cli_core_1.pathManager.getBackendDirPath();
      const tempDir = path.join(backendDir, '.temp');
      const currentCloudBackendDir = amplify_cli_core_1.pathManager.getCurrentCloudBackendDirPath();
      if (!fs.existsSync(tempDir)) {
          fs.mkdirSync(tempDir);
      }
      const tagFilePath = amplify_cli_core_1.pathManager.getTagFilePath();
      const tagCloudFilePath = amplify_cli_core_1.pathManager.getCurrentTagFilePath();
      if (fs.existsSync(tagFilePath)) {
          fs.copySync(tagFilePath, tagCloudFilePath, { overwrite: true });
      }
      const cliJSONFiles = glob_1.default.sync(amplify_cli_core_1.PathConstants.CLIJSONFileNameGlob, {
          cwd: amplify_cli_core_1.pathManager.getAmplifyDirPath(),
          absolute: true,
      });
      const zipFilePath = path.normalize(path.join(tempDir, zipFilename));
      let log = null;
      const result = await archiver_1.default.run(currentCloudBackendDir, zipFilePath, undefined, cliJSONFiles);
      const s3Key = `${result.zipFilename}`;
      const s3 = await aws_s3_1.S3.getInstance(context);
      const s3Params = {
          Body: fs.createReadStream(result.zipFilePath),
          Key: s3Key,
      };
      log = logger('storeCurrentcoudBackend.s3.uploadFile', [{ Key: s3Key }]);
      log();
      try {
          await s3.uploadFile(s3Params);
      }
      catch (error) {
          log(error);
          throw error;
      }
      fs.removeSync(tempDir);
  }
  exports.storeCurrentCloudBackend = storeCurrentCloudBackend;
  function validateCfnTemplates(context, resourcesToBeUpdated) {
      for (const { category, resourceName } of resourcesToBeUpdated) {
          const backEndDir = amplify_cli_core_1.pathManager.getBackendDirPath();
          const resourceDir = path.normalize(path.join(backEndDir, category, resourceName));
          const cfnFiles = glob_1.default.sync(cfnTemplateGlobPattern, {
              cwd: resourceDir,
              ignore: [parametersJson],
          });
          for (const cfnFile of cfnFiles) {
              const filePath = path.normalize(path.join(resourceDir, cfnFile));
              try {
                  cfn_lint_1.validateFile(filePath);
              }
              catch (err) {
                  context.print.error(`Invalid CloudFormation template: ${filePath}${os_1.EOL}${err.message}`);
              }
          }
      }
  }
  async function prepareBuildableResources(context, resources) {
      return Promise.all(resources.filter(resource => resource.build).map(resource => prepareResource(context, resource)));
  }
  async function prepareResource(context, resource) {
      resource.lastBuildTimeStamp = await context.amplify.invokePluginMethod(context, 'function', undefined, 'buildResource', [
          context,
          resource,
      ]);
      const result = await context.amplify.invokePluginMethod(context, 'function', undefined, 'packageResource', [context, resource]);
      const { envName } = context.amplify.getEnvInfo();
      const s3Key = `amplify-builds/${result.zipFilename}`;
      const s3 = await aws_s3_1.S3.getInstance(context);
      const s3Params = {
          Body: fs.createReadStream(result.zipFilePath),
          Key: s3Key,
      };
      const log = logger('packageResources.s3.uploadFile', [{ Key: s3Key }]);
      log();
      let s3Bucket;
      try {
          s3Bucket = await s3.uploadFile(s3Params);
      }
      catch (error) {
          log(error);
          throw error;
      }
      const { category, resourceName } = resource;
      const backEndDir = amplify_cli_core_1.pathManager.getBackendDirPath();
      const resourceDir = path.normalize(path.join(backEndDir, category, resourceName));
      const cfnFiles = glob_1.default.sync(cfnTemplateGlobPattern, {
          cwd: resourceDir,
          ignore: [parametersJson],
      });
      if (cfnFiles.length !== 1) {
          const errorMessage = cfnFiles.length > 1
              ? 'Only one CloudFormation template is allowed in the resource directory'
              : 'CloudFormation template is missing in the resource directory';
          context.print.error(errorMessage);
          context.print.error(resourceDir);
          throw new Error(errorMessage);
      }
      const cfnFile = cfnFiles[0];
      const cfnFilePath = path.normalize(path.join(resourceDir, cfnFile));
      const cfnMeta = amplify_cli_core_1.JSONUtilities.readJson(cfnFilePath);
      const paramType = { Type: 'String' };
      if (resource.service === FunctionServiceNameLambdaLayer) {
          cfnMeta.Parameters.deploymentBucketName = paramType;
          cfnMeta.Parameters.s3Key = paramType;
          storeS3BucketInfo(category, s3Bucket, envName, resourceName, s3Key);
      }
      else if (resource.service === ApiServiceNameElasticContainer) {
          const cfnParams = { ParamZipPath: s3Key };
          const cfnParamsFilePath = path.normalize(path.join(resourceDir, 'parameters.json'));
          amplify_cli_core_1.JSONUtilities.writeJson(cfnParamsFilePath, cfnParams);
      }
      else {
          cfnMeta.Parameters.deploymentBucketName = paramType;
          cfnMeta.Parameters.s3Key = paramType;
          if (cfnMeta.Resources.LambdaFunction.Type === 'AWS::Serverless::Function') {
              cfnMeta.Resources.LambdaFunction.Properties.CodeUri = {
                  Bucket: cloudform_types_1.Fn.Ref('deploymentBucketName'),
                  Key: cloudform_types_1.Fn.Ref('s3Key'),
              };
          }
          else {
              cfnMeta.Resources.LambdaFunction.Properties.Code = {
                  S3Bucket: cloudform_types_1.Fn.Ref('deploymentBucketName'),
                  S3Key: cloudform_types_1.Fn.Ref('s3Key'),
              };
          }
          storeS3BucketInfo(category, s3Bucket, envName, resourceName, s3Key);
      }
      amplify_cli_core_1.JSONUtilities.writeJson(cfnFilePath, cfnMeta);
  }
  function storeS3BucketInfo(category, deploymentBucketName, envName, resourceName, s3Key) {
      const amplifyMeta = amplify_cli_core_1.stateManager.getMeta();
      const teamProviderInfo = amplify_cli_core_1.stateManager.getTeamProviderInfo();
      const tpiResourceParams = lodash_1.default.get(teamProviderInfo, [envName, 'categories', category, resourceName], {});
      lodash_1.default.assign(tpiResourceParams, { deploymentBucketName, s3Key });
      lodash_1.default.set(teamProviderInfo, [envName, 'categories', category, resourceName], tpiResourceParams);
      lodash_1.default.set(amplifyMeta, [category, resourceName, 's3Bucket'], { deploymentBucketName, s3Key });
      amplify_cli_core_1.stateManager.setMeta(undefined, amplifyMeta);
      amplify_cli_core_1.stateManager.setTeamProviderInfo(undefined, teamProviderInfo);
  }
  async function updateCloudFormationNestedStack(context, nestedStack, resourcesToBeCreated, resourcesToBeUpdated) {
      const backEndDir = amplify_cli_core_1.pathManager.getBackendDirPath();
      const nestedStackFilepath = path.normalize(path.join(backEndDir, constants_1.ProviderName, nestedStackFileName));
      amplify_cli_core_1.JSONUtilities.writeJson(nestedStackFilepath, nestedStack);
      const transformedStackPath = await cfn_pre_processor_1.preProcessCFNTemplate(nestedStackFilepath);
      const cfnItem = await new aws_cfn_1.default(context, generateUserAgentAction(resourcesToBeCreated, resourcesToBeUpdated));
      const providerDirectory = path.normalize(path.join(backEndDir, constants_1.ProviderName));
      const log = logger('updateCloudFormationNestedStack', [providerDirectory, transformedStackPath]);
      try {
          log();
          await cfnItem.updateResourceStack(transformedStackPath);
      }
      catch (error) {
          log(error);
          throw error;
      }
  }
  function generateUserAgentAction(resourcesToBeCreated, resourcesToBeUpdated) {
      const uniqueCategoriesAdded = getAllUniqueCategories(resourcesToBeCreated);
      const uniqueCategoriesUpdated = getAllUniqueCategories(resourcesToBeUpdated);
      let userAgentAction = '';
      if (uniqueCategoriesAdded.length > 0) {
          uniqueCategoriesAdded.forEach(category => {
              if (category.length >= 2) {
                  category = category.substring(0, 2);
              }
              userAgentAction += `${category}:c `;
          });
      }
      if (uniqueCategoriesUpdated.length > 0) {
          uniqueCategoriesUpdated.forEach(category => {
              if (category.length >= 2) {
                  category = category.substring(0, 2);
              }
              userAgentAction += `${category}:u `;
          });
      }
      return userAgentAction;
  }
  function getAllUniqueCategories(resources) {
      const categories = new Set();
      resources.forEach(resource => categories.add(resource.category));
      return [...categories];
  }
  function getCfnFiles(category, resourceName) {
      const backEndDir = amplify_cli_core_1.pathManager.getBackendDirPath();
      const resourceDir = path.normalize(path.join(backEndDir, category, resourceName));
      const resourceBuildDir = path.join(resourceDir, optionalBuildDirectoryName);
      if (fs.existsSync(resourceBuildDir) && fs.lstatSync(resourceBuildDir).isDirectory()) {
          const cfnFiles = glob_1.default.sync(cfnTemplateGlobPattern, {
              cwd: resourceBuildDir,
              ignore: [parametersJson],
          });
          if (cfnFiles.length > 0) {
              return {
                  resourceDir: resourceBuildDir,
                  cfnFiles,
              };
          }
      }
      const cfnFiles = glob_1.default.sync(cfnTemplateGlobPattern, {
          cwd: resourceDir,
          ignore: [parametersJson, upload_auth_trigger_template_1.AUTH_TRIGGER_TEMPLATE],
      });
      return {
          resourceDir,
          cfnFiles,
      };
  }
  async function updateS3Templates(context, resourcesToBeUpdated, amplifyMeta) {
      var _a, _b, _c, _d;
      const promises = [];
      for (const { category, resourceName } of resourcesToBeUpdated) {
          const { resourceDir, cfnFiles } = getCfnFiles(category, resourceName);
          for (const cfnFile of cfnFiles) {
              const transformedCFNPath = await cfn_pre_processor_1.preProcessCFNTemplate(path.join(resourceDir, cfnFile));
              promises.push(uploadTemplateToS3(context, transformedCFNPath, category, resourceName, amplifyMeta));
          }
      }
      const { APIGatewayAuthURL } = (_d = (_c = (_b = (_a = context.amplify.getProjectDetails()) === null || _a === void 0 ? void 0 : _a.amplifyMeta) === null || _b === void 0 ? void 0 : _b.providers) === null || _c === void 0 ? void 0 : _c[constants_1.default.ProviderName]) !== null && _d !== void 0 ? _d : {};
      if (APIGatewayAuthURL) {
          const resourceDir = path.join(context.amplify.pathManager.getBackendDirPath(), 'api');
          promises.push(uploadTemplateToS3(context, path.join(resourceDir, `${consolidate_apigw_policies_1.APIGW_AUTH_STACK_LOGICAL_ID}.json`), 'api', '', null));
      }
      return Promise.all(promises);
  }
  async function uploadTemplateToS3(context, filePath, category, resourceName, amplifyMeta) {
      const cfnFile = path.parse(filePath).base;
      const s3 = await aws_s3_1.S3.getInstance(context);
      const s3Params = {
          Body: fs.createReadStream(filePath),
          Key: `amplify-cfn-templates/${category}/${cfnFile}`,
      };
      const log = logger('uploadTemplateToS3.s3.uploadFile', [{ Key: s3Params.Key }]);
      let projectBucket;
      try {
          projectBucket = await s3.uploadFile(s3Params, false);
      }
      catch (error) {
          log(error);
          throw error;
      }
      if (amplifyMeta) {
          const templateURL = `https://s3.amazonaws.com/${projectBucket}/amplify-cfn-templates/${category}/${cfnFile}`;
          const providerMetadata = amplifyMeta[category][resourceName].providerMetadata || {};
          providerMetadata.s3TemplateURL = templateURL;
          providerMetadata.logicalId = category + resourceName;
          context.amplify.updateamplifyMetaAfterResourceUpdate(category, resourceName, 'providerMetadata', providerMetadata);
      }
  }
  exports.uploadTemplateToS3 = uploadTemplateToS3;
  async function formNestedStack(context, projectDetails, categoryName, resourceName, serviceName, skipEnv) {
      var _a, _b;
      const initTemplateFilePath = path.join(__dirname, '..', 'resources', rootStackFileName);
      const nestedStack = amplify_cli_core_1.JSONUtilities.readJson(initTemplateFilePath);
      try {
          const amplifyMeta = amplify_cli_core_1.stateManager.getMeta();
          const appId = amplifyMeta.providers[constants_1.ProviderName].AmplifyAppId;
          if ((await admin_helpers_1.isAmplifyAdminApp(appId)).isAdminApp) {
              nestedStack.Description = 'Root Stack for AWS Amplify Console';
          }
      }
      catch (err) {
          console.info('App not deployed yet.');
      }
      const { amplifyMeta } = projectDetails;
      let authResourceName;
      const { APIGatewayAuthURL, NetworkStackS3Url, AuthTriggerTemplateURL } = amplifyMeta.providers[constants_1.default.ProviderName];
      if (APIGatewayAuthURL) {
          const stack = {
              Type: 'AWS::CloudFormation::Stack',
              Properties: {
                  TemplateURL: APIGatewayAuthURL,
                  Parameters: {
                      authRoleName: {
                          Ref: 'AuthRoleName',
                      },
                      unauthRoleName: {
                          Ref: 'UnauthRoleName',
                      },
                      env: context.exeInfo.localEnvInfo.envName,
                  },
              },
          };
          const apis = (_a = amplifyMeta === null || amplifyMeta === void 0 ? void 0 : amplifyMeta.api) !== null && _a !== void 0 ? _a : {};
          Object.keys(apis).forEach(apiName => {
              const api = apis[apiName];
              if (consolidate_apigw_policies_1.loadApiWithPrivacyParams(context, apiName, api)) {
                  stack.Properties.Parameters[apiName] = {
                      'Fn::GetAtt': [api.providerMetadata.logicalId, 'Outputs.ApiId'],
                  };
              }
          });
          nestedStack.Resources[consolidate_apigw_policies_1.APIGW_AUTH_STACK_LOGICAL_ID] = stack;
      }
      if (AuthTriggerTemplateURL) {
          const stack = {
              Type: 'AWS::CloudFormation::Stack',
              Properties: {
                  TemplateURL: AuthTriggerTemplateURL,
                  Parameters: {
                      env: context.exeInfo.localEnvInfo.envName,
                  },
              },
              DependsOn: [],
          };
          const authResource = (_b = amplifyMeta === null || amplifyMeta === void 0 ? void 0 : amplifyMeta.auth) !== null && _b !== void 0 ? _b : {};
          const authRootStackResourceName = `auth${Object.keys(authResource)[0]}`;
          stack.Properties.Parameters['userpoolId'] = {
              'Fn::GetAtt': [authRootStackResourceName, 'Outputs.UserPoolId'],
          };
          stack.Properties.Parameters['userpoolArn'] = {
              'Fn::GetAtt': [authRootStackResourceName, 'Outputs.UserPoolArn'],
          };
          stack.DependsOn.push(authRootStackResourceName);
          const { dependsOn } = authResource[Object.keys(authResource)[0]];
          dependsOn.forEach(resource => {
              const dependsOnStackName = resource.category + resource.resourceName;
              stack.DependsOn.push(dependsOnStackName);
              const dependsOnAttributes = resource === null || resource === void 0 ? void 0 : resource.attributes;
              dependsOnAttributes.forEach(attribute => {
                  const parameterKey = `${resource.category}${resource.resourceName}${attribute}`;
                  const parameterValue = { 'Fn::GetAtt': [dependsOnStackName, `Outputs.${attribute}`] };
                  stack.Properties.Parameters[parameterKey] = parameterValue;
              });
          });
          nestedStack.Resources[upload_auth_trigger_template_1.AUTH_TRIGGER_STACK] = stack;
      }
      if (NetworkStackS3Url) {
          nestedStack.Resources[stack_1.NETWORK_STACK_LOGICAL_ID] = {
              Type: 'AWS::CloudFormation::Stack',
              Properties: {
                  TemplateURL: NetworkStackS3Url,
              },
          };
          nestedStack.Resources.DeploymentBucket.Properties['VersioningConfiguration'] = {
              Status: 'Enabled',
          };
          nestedStack.Resources.DeploymentBucket.Properties['LifecycleConfiguration'] = {
              Rules: [
                  {
                      ExpirationInDays: 7,
                      NoncurrentVersionExpirationInDays: 7,
                      Prefix: 'codepipeline-amplify/',
                      Status: 'Enabled',
                  },
              ],
          };
      }
      let categories = Object.keys(amplifyMeta);
      categories = categories.filter(category => category !== 'provider');
      categories.forEach(category => {
          const resources = Object.keys(amplifyMeta[category]);
          resources.forEach(resource => {
              var _a, _b;
              const resourceDetails = amplifyMeta[category][resource];
              if (category === 'auth' && resource !== 'userPoolGroups') {
                  authResourceName = resource;
              }
              const resourceKey = category + resource;
              let templateURL;
              if (resourceDetails.providerPlugin) {
                  const parameters = resourceParams_1.loadResourceParameters(context, category, resource);
                  const { dependsOn } = resourceDetails;
                  if (dependsOn) {
                      for (let i = 0; i < dependsOn.length; i += 1) {
                          for (let j = 0; j < ((_b = (_a = dependsOn[i]) === null || _a === void 0 ? void 0 : _a.attributes) === null || _b === void 0 ? void 0 : _b.length) || 0; j += 1) {
                              let parameterValue;
                              const dependentResource = lodash_1.default.get(amplifyMeta, [dependsOn[i].category, dependsOn[i].resourceName], undefined);
                              if (!dependentResource && dependsOn[i].category) {
                                  throw new Error(`Cannot get resource: ${dependsOn[i].resourceName} from '${dependsOn[i].category}' category.`);
                              }
                              if (dependentResource && dependentResource.serviceType === 'imported') {
                                  const outputAttributeValue = lodash_1.default.get(dependentResource, ['output', dependsOn[i].attributes[j]], undefined);
                                  if (!outputAttributeValue) {
                                      const error = new Error(`Cannot read the '${dependsOn[i].attributes[j]}' dependent attribute value from the output section of resource: '${dependsOn[i].resourceName}'.`);
                                      error.stack = undefined;
                                      throw error;
                                  }
                                  parameterValue = outputAttributeValue;
                              }
                              else {
                                  const dependsOnStackName = dependsOn[i].category + dependsOn[i].resourceName;
                                  const parameterOutputValue = lodash_1.default.get(dependentResource, ['output', dependsOn[i].attributes[j]], undefined);
                                  parameterValue =
                                      parameterOutputValue === undefined
                                          ? { 'Fn::GetAtt': [dependsOnStackName, `Outputs.${dependsOn[i].attributes[j]}`] }
                                          : parameterOutputValue;
                              }
                              const parameterKey = `${dependsOn[i].category}${dependsOn[i].resourceName}${dependsOn[i].attributes[j]}`;
                              if (!isAuthTrigger(dependsOn[i])) {
                                  parameters[parameterKey] = parameterValue;
                              }
                          }
                          if (dependsOn[i].exports) {
                              Object.keys(dependsOn[i].exports)
                                  .map(key => ({ key, value: dependsOn[i].exports[key] }))
                                  .forEach(({ key, value }) => {
                                  parameters[key] = { 'Fn::ImportValue': value };
                              });
                          }
                      }
                  }
                  const values = Object.values(parameters);
                  const keys = Object.keys(parameters);
                  for (let a = 0; a < values.length; a += 1) {
                      if (Array.isArray(values[a])) {
                          parameters[keys[a]] = values[a].join();
                      }
                  }
                  const currentEnv = context.amplify.getEnvInfo().envName;
                  if (!skipEnv && resourceName) {
                      if (resource === resourceName && category === categoryName && amplifyMeta[category][resource].service === serviceName) {
                          Object.assign(parameters, { env: currentEnv });
                      }
                  }
                  else if (!skipEnv) {
                      Object.assign(parameters, { env: currentEnv });
                  }
                  const { imported, userPoolId, authRoleArn, authRoleName, unauthRoleArn, unauthRoleName, } = context.amplify.getImportedAuthProperties(context);
                  if (category !== 'auth' && resourceDetails.service !== 'Cognito' && imported) {
                      if (parameters.AuthCognitoUserPoolId) {
                          parameters.AuthCognitoUserPoolId = userPoolId;
                      }
                      if (parameters.authRoleArn) {
                          parameters.authRoleArn = authRoleArn;
                      }
                      if (parameters.authRoleName) {
                          parameters.authRoleName = authRoleName;
                      }
                      if (parameters.unauthRoleArn) {
                          parameters.unauthRoleArn = unauthRoleArn;
                      }
                      if (parameters.unauthRoleName) {
                          parameters.unauthRoleName = unauthRoleName;
                      }
                  }
                  if (resourceDetails.providerMetadata) {
                      templateURL = resourceDetails.providerMetadata.s3TemplateURL;
                      nestedStack.Resources[resourceKey] = {
                          Type: 'AWS::CloudFormation::Stack',
                          Properties: {
                              TemplateURL: templateURL,
                              Parameters: parameters,
                          },
                      };
                  }
              }
          });
      });
      if (authResourceName) {
          const importedAuth = lodash_1.default.get(amplifyMeta, ['auth', authResourceName], undefined);
          if (importedAuth && importedAuth.serviceType !== 'imported') {
              const authParameters = resourceParams_1.loadResourceParameters(context, 'auth', authResourceName);
              if (authParameters.identityPoolName) {
                  updateIdPRolesInNestedStack(nestedStack, authResourceName);
              }
          }
      }
      return nestedStack;
  }
  function updateIdPRolesInNestedStack(nestedStack, authResourceName) {
      const authLogicalResourceName = `auth${authResourceName}`;
      const idpUpdateRoleCfnFilePath = path.join(__dirname, '..', 'resources', 'update-idp-roles-cfn.json');
      const idpUpdateRoleCfn = amplify_cli_core_1.JSONUtilities.readJson(idpUpdateRoleCfnFilePath);
      idpUpdateRoleCfn.UpdateRolesWithIDPFunction.DependsOn.push(authLogicalResourceName);
      idpUpdateRoleCfn.UpdateRolesWithIDPFunctionOutputs.Properties.idpId['Fn::GetAtt'].unshift(authLogicalResourceName);
      Object.assign(nestedStack.Resources, idpUpdateRoleCfn);
  }
  async function generateAndUploadRootStack(context, destinationPath, destinationS3Key) {
      const projectDetails = context.amplify.getProjectDetails();
      const nestedStack = await formNestedStack(context, projectDetails);
      amplify_cli_core_1.JSONUtilities.writeJson(destinationPath, nestedStack);
      const s3Client = await aws_s3_1.S3.getInstance(context);
      const s3Params = {
          Body: Buffer.from(amplify_cli_core_1.JSONUtilities.stringify(nestedStack)),
          Key: destinationS3Key,
      };
      await s3Client.uploadFile(s3Params, false);
  }
  exports.generateAndUploadRootStack = generateAndUploadRootStack;
  function isAuthTrigger(dependsOnResource) {
      return (amplify_cli_core_1.FeatureFlags.getBoolean('auth.breakCircularDependency') &&
          dependsOnResource.category === 'function' &&
          dependsOnResource.triggerProvider === 'Cognito');
  }
  //# sourceMappingURL=push-resources.js.map