src/commands/init/index.js

Summary

Maintainability
A
1 hr
Test Coverage
import path from 'path';

import { spawn } from 'cross-spawn';
import inquirer from 'inquirer';
import ora from 'ora';
import { copySync } from 'fs-extra';
import semver from 'semver';
import chalk from 'chalk';

import log from '../../log/default/small';
import fileExists from '../../helpers/fileExists';
import folderExists from '../../helpers/folderExists';
import getPackageJSON from '../../helpers/getPackageJSON';

import { getVersions, getOfficialTemplates } from './githubHelpers';
import generateTemplate from './generateTemplate';
import checkFolder from './checkFolder';
import fetchTemplate, { isGitHub, isLocal } from './fetchTemplate';

export default async function init({
    arguments: { managed: managedArguments },
    options: { managed: managedOptions },
    context: { directory, verbose },
}) {
    const { 'list-versions': listVersions, force, clone } = managedOptions;
    const { name, version } = managedArguments;
    let { template } = managedArguments;

    // 0. Show a list of official templates if no template name was provided
    if (!template) {
        template = await showListOfTemplates();
    }

    if (listVersions) {
        if (isLocal(template, directory)) {
            return log.info('You can’t list versions for local templates');
        } else if (template.indexOf('/') === -1 || isGitHub(template)) {
            try {
                const templateVersions = await getVersions(template);

                // Add master so we always have a way to install it
                templateVersions.push({ name: 'master' });

                log.log('The available versions are:');
                return log.log(
                    templateVersions
                        .map(({ name: templateVersion }) => ` ${templateVersion}`).join('\n')
                );
            } catch (error) {
                log.error('Could not get the versions for the requested template', error);
            }
        }

        return log.info('Can’t list versions for the provided template');
    }

    // 1. Check if directory is empty
    const { dir, folderName } = await checkFolder(name, directory, { force });

    // 3. Download the template
    const templateDir = await fetchTemplate(template, directory, version, { clone });

    // 4. Validate the template
    validateTemplateDirectory(templateDir);

    // 5. Install potential dependencies that the template have
    await installTemplateDependencies(templateDir, { verbose });

    validateRequiredVersion(templateDir);

    // 5. Process template
    generateTemplate(folderName, templateDir, dir, () => {
        // Copy & Rename the template package.json if it exists, for history/documentation purposes
        if (fileExists('package.json', templateDir)) {
            copySync(path.join(templateDir, 'package.json'), path.join(dir, '.roc'));
        }
    });
}

function validateTemplateDirectory(templateDir) {
    if (!folderExists(path.join(templateDir, 'template'))) {
        log.error('Seems like this is not a Roc template.');
    }
}

function validateRequiredVersion(templateDir) {
    // This will eventually be defined by the roc-plugin-create
    const createVersion = '1.0.0';

    const checkVersion = (file) => {
        if (fileExists(file)) {
            const { required } = require(file); // eslint-disable-line
            if (required && !semver.satisfies(createVersion, required)) {
                log.error('The template requires another version of the template logic\n' +
                    `  ${chalk.bold('Requested:')} ${required} ${chalk.bold('Current:')} ${createVersion}`);
            }
        }
    };

    checkVersion(path.join(templateDir, 'roc.setup.json'));
    checkVersion(path.join(templateDir, 'roc.setup.js'));
}

async function installTemplateDependencies(templateDir, { verbose }) {
    if (
        fileExists('roc.setup.js', templateDir) &&
        fileExists('package.json', templateDir) &&
        Object.keys(getPackageJSON(templateDir).dependencies || {}).length > 0
    ) {
        const spinner = ora('Installing template setup dependencies from npm, this may take a little while').start();
        try {
            await npmInstall(templateDir, verbose);
        } catch (error) {
            spinner.fail();
        }
        spinner.succeed();
    }
}

function npmInstall(dirPath, verbose) {
    return new Promise((resolve, reject) => {
        // Run npm install
        const npm = spawn('npm', ['install', `--loglevel=${verbose ? 'verbose' : 'silent'}`], {
            cwd: dirPath,
            stdio: verbose && 'inherit',
        });

        npm.on('close', (code) => {
            if (code !== 0) {
                return reject(new Error('npm install failed with status code: ' + code));
            }

            return resolve();
        });
    });
}

// TODO Handle if we get 403 from GitHub
async function showListOfTemplates() {
    const spinner = ora('Fetching list of official templates').start();
    const templates = await getOfficialTemplates();
    spinner.stop();

    const choices = templates.map((template) => ({
        name: `${template.name}${template.description && ' - ' + template.description}`,
        short: template.name,
        value: template.full_name,
    }));

    const { option } = await inquirer.prompt([{
        type: 'rawlist',
        name: 'option',
        message: 'Select a template',
        choices,
    }]);

    return option;
}