Home Reference Source Repository

src/commands/init.js

import 'source-map-support/register';

import fs from 'fs-extra';
import path from 'path';
import { spawn } from 'child_process';
import inquirer from 'inquirer';
import replace from 'replace';
import chalk from 'chalk';

import { get, getVersions } from './helpers/github';
import { validRocProject } from './helpers/general';
import { error as styleError, warning, important, ok } from '../helpers/style';

/* This should be fetched from a server!
 */
const templates = [{
    name: 'Simple Roc App',
    description: 'A simple start on a generic web application',
    identifier: 'web',
    repo: 'vgno/roc-template-web'
}, {
    name: 'Simple Roc React App',
    description: 'A simple start on a React web application',
    identifier: 'web-react',
    repo: 'vgno/roc-template-web-react'
}];

/**
 * Command used to init a new Roc project.
 *
 * @param {rocCommandObject} parsedArguments - The Roc command object, uses parsedArguments from it.
 *
 * @returns {Promise} - Promise for the command.
 */
export default function init({ parsedOptions }) {
    const { template, version } = parsedOptions.options;

    // Make sure the directory is empty!
    assertEmptyDir();

    if (!template) {
        return interativeMenu();
    }

    return fetchTemplate(template, version);

    /*
     * Helpers
     */
    function fetchTemplate(toFetch, selectVersion) {
        if (toFetch.indexOf('/') === -1) {
            const selectedTemplate = templates.find((elem) => elem.identifier === toFetch);
            if (!selectedTemplate) {
                console.log(styleError('Invalid template name given.'));
                /* eslint-disable no-process-exit */
                process.exit(1);
                /* eslint-enable */
            }

            toFetch = selectedTemplate.repo;
        }

        return getVersions(toFetch)
            .then((versions) => {
                // If the name starts with a number we will automatically add 'v' infront of it to match Github default
                if (selectVersion && !isNaN(Number(selectVersion.charAt(0))) && selectVersion.charAt(0) !== 'v') {
                    selectVersion = `v${selectVersion}`;
                }

                const selectedVersion = versions.find((v) => v.name === selectVersion);
                const actualVersion = selectedVersion && selectedVersion.name ||
                    versions[0] && versions[0].name ||
                    'master';

                if (!selectedVersion && selectVersion) {
                    console.log(
                        warning(`Selected template version not found, using ${chalk.bold(actualVersion)}`)
                    );
                } else if (!selectedVersion) {
                    console.log(important(`Using ${chalk.bold(actualVersion)} as template version`));
                }

                return get(toFetch, actualVersion);
            })
            .then((dirPath) => {
                if (!validRocProject(path.join(dirPath, 'template'))) {
                    /* eslint-disable no-process-exit */
                    console.log(styleError('Seems like this is not a Roc template.'));
                    process.exit(1);
                    /* eslint-enable */
                } else {
                    console.log('\nInstalling template setup dependencies…');
                    return npmInstall(dirPath);
                }
            })
            .then((dirPath) => {
                inquirer.prompt(getPrompt(dirPath), (answers) => {
                    replaceTemplatedValues(answers, dirPath);
                    configureFiles(dirPath);

                    console.log(`\nInstalling template dependencies… ` +
                        `${chalk.dim('(If this fails you can always try to run npm install directly)')}`);
                    return npmInstall().then(() => {
                        console.log(ok('\nSetup completed!\n'));
                        console.log(`Start in dev mode by typing ${chalk.bold('roc dev')}`);
                    });
                });
            })
            .catch((error) => {
                console.log(styleError('\nAn error occured during init!\n'));
                console.error(error.stack);
                /* eslint-disable no-process-exit */
                process.exit(1);
                /* eslint-enable */
            });
    }

    function getPrompt(dirPath) {
        try {
            return require(path.join(dirPath, 'roc.setup.js')).prompt;
        } catch (error) {
            return require('./helpers/default-prompt').defaultPrompt;
        }
    }

    function replaceTemplatedValues(answers, dirPath) {
        Object.keys(answers).map((key) => {
            replace({
                regex: `{{\\s*${key}*\\s*}}`,
                replacement: answers[key],
                paths: [dirPath + '/template'],
                recursive: true,
                silent: true
            });
        });
    }

    function configureFiles(dirPath) {
        // Rename package.json to .roc for history purposes
        fs.renameSync(path.join(dirPath, 'package.json'), path.join(dirPath, 'template', '.roc'));

        // Move everything inside template to the current working directory
        fs.copySync(path.join(dirPath, 'template'), process.cwd());
    }

    function npmInstall(dirPath) {
        return new Promise((resolve, reject) => {
            dirPath = dirPath || process.cwd();
            // Run npm install
            const npm = spawn('npm', ['install'], {
                cwd: dirPath,
                stdio: 'inherit'
            });

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

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

    function interativeMenu() {
        return new Promise((resolve) => {
            const choices = templates.map((elem) => ({ name: elem.name, value: elem.identifier }));

            inquirer.prompt([{
                type: 'rawlist',
                name: 'option',
                message: 'Selected a type',
                choices: choices
            }], answers => {
                resolve(fetchTemplate(answers.option));
            });
        });
    }

    function assertEmptyDir() {
        if (fs.readdirSync(process.cwd()).length > 0) {
            console.log(styleError('You need to call this command from an empty directory.'));
            /* eslint-disable no-process-exit */
            process.exit(1);
            /* eslint-enable */
        }
    }
}