twilio-labs/create-twilio-function

View on GitHub
src/create-twilio-function.js

Summary

Maintainability
B
6 hrs
Test Coverage
const { promisify } = require('util');
const path = require('path');

const ora = require('ora');
const boxen = require('boxen');
const rimraf = promisify(require('rimraf'));
const { downloadTemplate } = require('twilio-run/dist/templating/actions');

const { promptForAccountDetails, promptForProjectName } = require('./create-twilio-function/prompt');
const validateProjectName = require('./create-twilio-function/validate-project-name');
const {
  createDirectory,
  createEnvFile,
  createExampleFromTemplates,
  createPackageJSON,
  createNvmrcFile,
  createTsconfigFile,
  createEmptyFileStructure,
} = require('./create-twilio-function/create-files');
const createGitignore = require('./create-twilio-function/create-gitignore');
const importCredentials = require('./create-twilio-function/import-credentials');
const { installDependencies } = require('./create-twilio-function/install-dependencies');
const successMessage = require('./create-twilio-function/success-message');

async function cleanUpAndExit(projectDir, spinner, errorMessage) {
  spinner.fail(errorMessage);
  spinner.start('Cleaning up project directories and files');
  await rimraf(projectDir);
  spinner.stop().clear();
  process.exitCode = 1;
}

async function performTaskWithSpinner(spinner, message, task) {
  spinner.start(message);
  await task();
  spinner.succeed();
}

async function createTwilioFunction(config) {
  const { valid, errors } = validateProjectName(config.name);
  if (!valid) {
    const { name } = await promptForProjectName(errors);
    config.name = name;
  }
  const projectDir = path.join(config.path, config.name);
  const projectType = config.typescript ? 'typescript' : 'javascript';
  const spinner = ora();

  try {
    await performTaskWithSpinner(spinner, 'Creating project directory', async () => {
      await createDirectory(config.path, config.name);
    });
  } catch (e) {
    if (e.code === 'EEXIST') {
      spinner.fail(
        `A directory called '${config.name}' already exists. Please create your function in a new directory.`,
      );
    } else if (e.code === 'EACCES') {
      spinner.fail(`You do not have permission to create files or directories in the path '${config.path}'.`);
    } else {
      spinner.fail(e.message);
    }
    process.exitCode = 1;
    return;
  }

  // Check to see if the request is valid for a template or an empty project
  if (config.empty && config.template) {
    await cleanUpAndExit(
      projectDir,
      spinner,
      'You cannot scaffold an empty Functions project with a template. Please choose empty or a template.',
    );
    return;
  }
  // Check to see if the project wants typescript and a template
  if (config.template && projectType === 'typescript') {
    await cleanUpAndExit(
      projectDir,
      spinner,
      'There are no TypeScript templates available. You can generate an example project or an empty one with the --empty flag.',
    );
    return;
  }

  // Get account sid and auth token
  let accountDetails = await importCredentials(config);
  if (Object.keys(accountDetails).length === 0) {
    accountDetails = await promptForAccountDetails(config);
  }
  config = { ...accountDetails, ...config };

  // Scaffold project
  spinner.start('Creating project directories and files');

  await createEnvFile(projectDir, {
    accountSid: config.accountSid,
    authToken: config.authToken,
  });
  await createNvmrcFile(projectDir);
  await createPackageJSON(projectDir, config.name, projectType);
  if (projectType === 'typescript') {
    await createTsconfigFile(projectDir);
  }
  if (config.template) {
    spinner.succeed();
    spinner.start(`Downloading template: "${config.template}"`);
    await createDirectory(projectDir, 'functions');
    await createDirectory(projectDir, 'assets');
    try {
      await downloadTemplate(config.template, '', projectDir);
    } catch (err) {
      await cleanUpAndExit(projectDir, spinner, `The template "${config.template}" doesn't exist`);
      return;
    }
  } else if (config.empty) {
    await createEmptyFileStructure(projectDir, projectType);
  } else {
    await createExampleFromTemplates(projectDir, projectType);
  }
  spinner.succeed();

  // Download .gitignore file from https://github.com/github/gitignore/
  try {
    await performTaskWithSpinner(spinner, 'Downloading .gitignore file', async () => {
      await createGitignore(projectDir);
    });
  } catch (err) {
    cleanUpAndExit(projectDir, spinner, 'Could not download .gitignore file');
    return;
  }

  // Install dependencies with npm
  try {
    await performTaskWithSpinner(spinner, 'Installing dependencies', async () => {
      await installDependencies(projectDir);
    });
  } catch (err) {
    spinner.fail();
    console.log(
      `There was an error installing the dependencies, but your project is otherwise complete in ./${config.name}`,
    );
  }

  // Success message

  console.log(boxen(await successMessage(config), { padding: 1, borderStyle: 'round' }));
}

module.exports = createTwilioFunction;