ForestAdmin/toolbelt

View on GitHub
src/commands/init.js

Summary

Maintainability
A
2 hrs
Test Coverage
A
98%
const { Flags } = require('@oclif/core');
const AbstractAuthenticatedCommand = require('../abstract-authenticated-command').default;
const withCurrentProject = require('../services/with-current-project');
const ProjectManager = require('../services/project-manager');
const EnvironmentManager = require('../services/environment-manager');
const {
  handleInitError,
  handleDatabaseConfiguration,
  validateEndpoint,
  getApplicationPortFromCompleteEndpoint,
  amendDotenvFile,
  createDotenvFile,
  displayEnvironmentVariablesAndCopyToClipboard,
} = require('../services/init-manager');

const SUCCESS_MESSAGE_ALL_SET_AND_READY = "You're now set up and ready to develop on Forest Admin";
const SUCCESS_MESSAGE_LEARN_MORE_ON_CLI_USAGE =
  'To learn more about the recommended usage of this CLI, please visit https://docs.forestadmin.com/documentation/reference-guide/how-it-works/developing-on-forest-admin/forest-cli-commands.';

const PROMPT_MESSAGE_AUTO_FILLING_ENV_FILE =
  'Do you want your current folder `.env` file to be completed automatically with your environment variables?';
const PROMPT_MESSAGE_AUTO_CREATING_ENV_FILE =
  'Do you want a new `.env` file (containing your environment variables) to be automatically created in your current folder?';

class InitCommand extends AbstractAuthenticatedCommand {
  constructor(argv, config, plan) {
    super(argv, config, plan);
    const { assertPresent, env, fs, inquirer, spinner, buildDatabaseUrl } = this.context;
    assertPresent({
      env,
      fs,
      inquirer,
      spinner,
      buildDatabaseUrl,
    });

    this.env = env;
    this.fs = fs;
    this.inquirer = inquirer;
    this.spinner = spinner;
    this.buildDatabaseUrl = buildDatabaseUrl;

    this.environmentVariables = {};
  }

  async runAuthenticated() {
    try {
      this.spinner.start({ text: 'Selecting your project' });
      await this.spinner.attachToPromise(this.projectSelection());

      this.spinner.start({ text: 'Analyzing your setup' });
      await this.spinner.attachToPromise(this.projectValidation());

      this.spinner.start({ text: 'Checking your database setup' });
      await this.spinner.attachToPromise(this.handleDatabaseUrlConfiguration());

      this.spinner.start({ text: 'Setting up your development environment' });
      await this.spinner.attachToPromise(this.developmentEnvironmentCreation());

      await this.environmentVariablesAutoFilling();

      this.spinner.start({ text: SUCCESS_MESSAGE_ALL_SET_AND_READY });
      this.spinner.success();
      this.logger.info(SUCCESS_MESSAGE_LEARN_MORE_ON_CLI_USAGE);
    } catch (error) {
      const exitMessage = handleInitError(error);
      this.logger.error(exitMessage);
      this.exit(1);
    }
  }

  async projectSelection() {
    const parsed = await this.parse(InitCommand);
    this.config = await withCurrentProject({
      ...this.env,
      ...parsed.flags,
      includeLegacy: true,
    });
  }

  async projectValidation() {
    const project = await new ProjectManager(this.config).getProjectForDevWorkflow();
    this.environmentVariables.projectOrigin = project.origin;
  }

  async handleDatabaseUrlConfiguration() {
    if (this.environmentVariables.projectOrigin !== 'In-app') {
      const isDatabaseAlreadyConfigured = !!this.env.DATABASE_URL;

      if (!isDatabaseAlreadyConfigured) {
        this.spinner.pause();
        const databaseConfiguration = await handleDatabaseConfiguration();
        this.spinner.continue();
        if (databaseConfiguration) {
          const dbConfig = {
            dbConnectionUrl: databaseConfiguration.dbConnectionUrl,
            dbDialect: databaseConfiguration.databaseDialect,
            dbHostname: databaseConfiguration.databaseHost,
            dbName: databaseConfiguration.databaseName,
            dbPassword: databaseConfiguration.databasePassword,
            dbPort: databaseConfiguration.databasePort,
            dbSchema: databaseConfiguration.databaseSchema,
            dbUser: databaseConfiguration.databaseUser,
            mongodbSrv: databaseConfiguration.mongodbSrv,
            ssl: databaseConfiguration.databaseSSL,
          };
          this.environmentVariables.databaseUrl = this.buildDatabaseUrl(dbConfig);
          this.environmentVariables.databaseSchema = databaseConfiguration.databaseSchema;
          this.environmentVariables.databaseSSL = databaseConfiguration.ssl;
        }
      }
    }
  }

  async developmentEnvironmentCreation() {
    let developmentEnvironment;
    try {
      developmentEnvironment = await new ProjectManager(
        this.config,
      ).getDevelopmentEnvironmentForUser(this.config.projectId);
    } catch (error) {
      developmentEnvironment = null;
    }

    if (!developmentEnvironment) {
      this.spinner.pause();
      const prompter = await this.inquirer.prompt([
        {
          name: 'endpoint',
          message: 'Enter your local admin backend endpoint:',
          type: 'input',
          default: 'http://localhost:3310',
          validate: validateEndpoint,
        },
      ]);
      this.spinner.continue();

      developmentEnvironment = await new EnvironmentManager(
        this.config,
      ).createDevelopmentEnvironment(this.config.projectId, prompter.endpoint);
    }
    this.environmentVariables.forestEnvSecret = developmentEnvironment.secretKey;
    this.environmentVariables.applicationPort = getApplicationPortFromCompleteEndpoint(
      developmentEnvironment.apiEndpoint,
    );
  }

  async environmentVariablesAutoFilling() {
    if (this.environmentVariables.projectOrigin !== 'In-app') {
      const existingEnvFile = this.fs.existsSync('.env');
      const response = await this.inquirer.prompt([
        {
          type: 'confirm',
          name: 'autoFillOrCreationConfirmation',
          message: existingEnvFile
            ? PROMPT_MESSAGE_AUTO_FILLING_ENV_FILE
            : PROMPT_MESSAGE_AUTO_CREATING_ENV_FILE,
        },
      ]);
      if (response.autoFillOrCreationConfirmation) {
        try {
          return existingEnvFile
            ? amendDotenvFile(this.environmentVariables)
            : createDotenvFile(this.environmentVariables);
        } catch (error) {
          return displayEnvironmentVariablesAndCopyToClipboard(this.environmentVariables);
        }
      }
    }
    return displayEnvironmentVariablesAndCopyToClipboard(this.environmentVariables);
  }
}

InitCommand.aliases = ['environments:init'];

InitCommand.description = 'Set up your development environment in your current folder.';

InitCommand.flags = {
  projectId: Flags.integer({
    char: 'p',
    description: 'The id of the project you want to init.',
  }),
};

module.exports = InitCommand;