fsBuilder service


import readline from 'readline';
import path from 'path';
import chalk from 'chalk';
import fs from 'fs';
import { promisify } from 'util';

export default {
    create(){
        let outputBuffer = [];
    
        const echo = (stringable) => {
            outputBuffer.push(`${stringable}`);
        };
    
        const grab = (fn) => {
            const previousOutputBuffer = outputBuffer;
            outputBuffer = [];
            fn();
            const out = outputBuffer;
            outputBuffer = previousOutputBuffer;
            return out;
        };
    
        const line = (stringable = '') => {
            if(outputBuffer.length){
                echo(`\n${stringable}`);
            } else {
                echo(stringable);
            }
        };
    
        const indent = (fn) => {
            const lines = grab(fn);
            if(!lines.length){
                return;
            }
            lines.join('').split(/\n/).forEach((string) => {
                line(`    ${string}`);
            });
        };
    
        const confirm = async (question) => {
            while(true){
                const answer = (await new Promise((resolve) => {
                    const rl = readline.createInterface({
                        input: process.stdin,
                        output: process.stdout
                    });
                    rl.question(`${question.replace(/\?+$/, '')}? Y/n/a: `, answer => {
                        rl.close();
                        resolve(answer);
                    });
                })).toLowerCase();
                
                if(answer == '' || answer == 'y'){
                    return true;
                } else if(answer == 'n'){
                    return false;
                } else if(answer == 'a'){
                    process.exit();
                }
            }
        };
    
    
        const generateDir = async (dirPath, fn = () => {}) => {
            if(!path.isAbsolute(dirPath)){
                dirPath = path.normalize(`${process.cwd()}/${dirPath}`);
            }
            if(!fs.existsSync(dirPath)){
                console.log(chalk.green(`Creating dir "${dirPath}"`));
                const dirSegments = dirPath.replace(/^\//, '').split(/\//);
                const currentDirSegments = [];
                while(dirSegments.length){
                    currentDirSegments.push(dirSegments.shift());
                    const currentPath = `/${currentDirSegments.join('/')}`;
                    if(!fs.existsSync(currentPath)){
                        await promisify(fs.mkdir)(currentPath);
                    }
                }
            }
    
            const previousDirPath = process.cwd();
            process.chdir(dirPath);
            await fn();
            process.chdir(previousDirPath);
        };
    
        const generateFile = async (name, ...args) => {
            const options = typeof args[0] == 'object' ? args.shift() : {};
            const fn = typeof args[0] == 'function' ? args.shift() : () => {};
            const matches = name.match(/^(.*)\/([^\/]*)$/);
            if(matches){
                await generateDir(matches[1], () => generateFile(matches[2], options, fn));
            } else {
                const data = (await grab(fn)).join('');
                const filePath = `${process.cwd()}/${name}`;
    
                if(!fs.existsSync(filePath)){
                    console.log(chalk.green(`Creating file "${filePath}"`));
                    await promisify(fs.writeFile)(name, data);
                } else if(await promisify(fs.readFile)(name) == data){
                    // no change (so ignore)
                } else if(options.skipIfExists){
                    console.log(chalk.blue(`Skipping file "${filePath}" (as it already exists)`));
                } else if(options.force || await confirm(`Are you sure you want to update ${filePath}?`)){
                    console.log(chalk.red(`Updating file "${filePath}"`));
                    await promisify(fs.writeFile)(name, data);
                }
            }
        };
    
        const inProjectRootDir = async (fn) => {
            const previousDirPath = process.cwd();
            process.chdir(await this.project.rootPath);
            await fn();
            process.chdir(previousDirPath);
        };
    
        return {
            echo,
            grab,
            line,
            indent,
            confirm,
            generateDir,
            generateFile,
            inProjectRootDir
        };
    }
};