ForestAdmin/toolbelt

View on GitHub
src/services/branch-manager.js

Summary

Maintainability
C
7 hrs
Test Coverage
A
95%
const Context = require('@forestadmin/context');

const branchDeserializer = require('../deserializers/branch');
const EnvironmentSerializer = require('../serializers/environment');
const { handleErrorWithMeta } = require('../utils/error');
const DiffCommand = require('../commands/schema/diff');

const ERROR_MESSAGE_PROJECT_IN_V1 =
  'This project does not support branches yet. Please migrate your environments from your Project settings first.';
const ERROR_MESSAGE_ENV_SECRET_ISSUE =
  'Your development environment is not properly set up. Please run `forest init` first and retry.';
const ERROR_MESSAGE_BRANCH_ALREADY_EXISTS = 'This branch already exists.';
const ERROR_MESSAGE_ADDITIONAL_REMOTE_BRANCHES =
  "The remote environments can't have additional branches.";
const ERROR_MESSAGE_NO_PRODUCTION_OR_REMOTE_ENVIRONMENT =
  'You cannot run branch commands until this project has either a remote or a production environment.';
const ERROR_MESSAGE_NO_REMOTE_ENVIRONMENT =
  'You cannot run this command until this project has a remote non-production environment.';
const ERROR_MESSAGE_BRANCH_DOES_NOT_EXIST = "This branch doesn't exist.";
const ERROR_MESSAGE_REMOVE_BRANCH_FAILED = 'Failed to delete branch.';
const ERROR_MESSAGE_NOT_RIGHT_PERMISSION_LEVEL =
  "You need the 'Admin' or 'Developer' permission level on this project to use branches.";
const ERROR_MESSAGE_ENVIRONMENT_NOT_FOUND = "The environment provided doesn't exist.";
const ERROR_MESSAGE_NO_CURRENT_BRANCH =
  "You don't have any branch to push. Use `forest branch` to create one or use `forest switch` to set your current branch.";
const ERROR_MESSAGE_WRONG_ENVIRONMENT_TYPE =
  'The environment on which you are trying to push your modifications is not a remote environment.';
const ERROR_MESSAGE_NO_DESTINATION_BRANCH =
  "The environment on which you are trying to push your modifications doesn't have current branch.";

function getBranches(envSecret) {
  const { assertPresent, authenticator, env, superagent: agent } = Context.inject();
  assertPresent({ authenticator, env, superagent: agent });
  const authToken = authenticator.getAuthToken();
  return agent
    .get(`${env.FOREST_SERVER_URL}/api/branches`)
    .set('Authorization', `Bearer ${authToken}`)
    .set('forest-secret-key', envSecret)
    .send()
    .then(response => branchDeserializer.deserialize(response.body));
}

function deleteBranch(branchName, environmentSecret) {
  const { assertPresent, authenticator, env, superagent: agent } = Context.inject();
  assertPresent({ authenticator, env, superagent: agent });
  const authToken = authenticator.getAuthToken();

  return agent
    .del(`${env.FOREST_SERVER_URL}/api/branches/${encodeURIComponent(branchName)}`)
    .set('Authorization', `Bearer ${authToken}`)
    .set('forest-secret-key', `${environmentSecret}`)
    .send();
}

function createBranch(branchName, environmentSecret, originName) {
  const { assertPresent, authenticator, env, superagent: agent } = Context.inject();
  assertPresent({ authenticator, env, superagent: agent });
  const authToken = authenticator.getAuthToken();

  return agent
    .post(`${env.FOREST_SERVER_URL}/api/branches`)
    .set('Authorization', `Bearer ${authToken}`)
    .set('forest-secret-key', `${environmentSecret}`)
    .send({
      branchName: encodeURIComponent(branchName),
      originName: encodeURIComponent(originName),
    });
}

function pushBranch(environmentSecret) {
  const { assertPresent, authenticator, env, superagent: agent } = Context.inject();
  assertPresent({ authenticator, env, superagent: agent });
  const authToken = authenticator.getAuthToken();

  return agent
    .post(`${env.FOREST_SERVER_URL}/api/branches/push`)
    .set('Authorization', `Bearer ${authToken}`)
    .set('forest-secret-key', `${environmentSecret}`)
    .send();
}

function switchBranch({ id }, environmentSecret) {
  const { assertPresent, authenticator, env, superagent: agent } = Context.inject();
  assertPresent({ authenticator, env, superagent: agent });
  const authToken = authenticator.getAuthToken();

  return agent
    .put(`${env.FOREST_SERVER_URL}/api/environments`)
    .set('Authorization', `Bearer ${authToken}`)
    .set('forest-secret-key', `${environmentSecret}`)
    .send(EnvironmentSerializer.serialize({ currentBranchId: id }));
}

function setOrigin(originEnvironmentName, environmentSecret) {
  const { assertPresent, authenticator, env, superagent: agent } = Context.inject();
  assertPresent({ authenticator, env, superagent: agent });
  const authToken = authenticator.getAuthToken();

  return agent
    .post(`${env.FOREST_SERVER_URL}/api/branches/set-origin`)
    .set('Authorization', `Bearer ${authToken}`)
    .set('forest-secret-key', `${environmentSecret}`)
    .send({ originEnvironmentName: encodeURIComponent(originEnvironmentName) });
}

async function handleBranchError(rawError) {
  const { message, meta } = handleErrorWithMeta(rawError);
  switch (message) {
    // NOTICE: When no env/project can be found through envSecret
    case 'Not Found':
      return ERROR_MESSAGE_ENV_SECRET_ISSUE;
    case 'Forbidden':
      return ERROR_MESSAGE_NOT_RIGHT_PERMISSION_LEVEL;
    case 'Environment is not in development.':
      return ERROR_MESSAGE_ADDITIONAL_REMOTE_BRANCHES;
    case 'Dev Workflow disabled.':
      return ERROR_MESSAGE_PROJECT_IN_V1;
    case 'Branch name already exists.':
      return ERROR_MESSAGE_BRANCH_ALREADY_EXISTS;
    case 'No production/remote environment.':
      return ERROR_MESSAGE_NO_PRODUCTION_OR_REMOTE_ENVIRONMENT;
    case 'No remote environment.':
      return ERROR_MESSAGE_NO_REMOTE_ENVIRONMENT;
    case 'Branch does not exist.':
      return ERROR_MESSAGE_BRANCH_DOES_NOT_EXIST;
    case 'Failed to remove branch.':
      return ERROR_MESSAGE_REMOVE_BRANCH_FAILED;
    case 'Environment not found.':
      return ERROR_MESSAGE_ENVIRONMENT_NOT_FOUND;
    case 'No current branch.':
      return ERROR_MESSAGE_NO_CURRENT_BRANCH;
    case 'Environment type should be remote.':
      return ERROR_MESSAGE_WRONG_ENVIRONMENT_TYPE;
    case 'No destination branch.':
      return ERROR_MESSAGE_NO_DESTINATION_BRANCH;
    case 'Source and destination environments must have the same schema. Please check your environments code is synchronized.':
      await new DiffCommand([
        `${meta.environmentIdSource}`,
        `${meta.environmentIdDestination}`,
      ]).runAuthenticated();
      return `Source and destination environments must have the same schema. Please check your environments code is synchronized.\n You can run "schema:diff ${meta.environmentIdSource} ${meta.environmentIdDestination}" to see the schemas differences.`;
    default:
      return message;
  }
}

module.exports = {
  getBranches,
  deleteBranch,
  createBranch,
  pushBranch,
  switchBranch,
  setOrigin,
  handleBranchError,
};