@bitpshr.net/caster
Version:
Caster generates package skeletons pre-configured for testing, linting, and formatting.
165 lines (149 loc) • 4.79 kB
JavaScript
/* eslint-disable no-console */
const chalk = require('chalk');
const ejs = require('ejs');
const figlet = require('figlet');
const fs = require('fs-extra');
const inquirer = require('inquirer');
const ora = require('ora');
const path = require('path');
const read = require('recursive-readdir');
const shell = require('shelljs');
const CSS_WHITELIST = ['.stylelintrc'];
const JS_WHITELIST = ['.eslintrc.ejs', '.jestrc.json.ejs'];
const REACT_WHITELIST = ['testSetup.js'];
/**
* Collects user input for app configuration
*
* @returns {Promise<Object>} - resolves to a config obejct
*/
function collectInput() {
return inquirer.prompt([
{
default: 'my-package',
message: "What's the package name?",
name: 'name',
type: 'input'
},
{
default: '',
message: "What's the package description?",
name: 'description',
type: 'input'
},
{
default: [],
message: 'What technologies does the package use?',
name: 'tech',
type: 'checkbox',
choices: ['css', 'js', 'react']
}
]);
}
/**
* Utility function to sanitize config input
*
* @param {object} config - user input configuration object
* @returns {object} - user input configuration object
*/
function sanitizeInput(config) {
config.tech = {
css: config.tech.includes('css'),
js: config.tech.includes('js'),
react: config.tech.includes('react')
};
return config;
}
/**
* Utility function to sanitize EJS file endings
*
* @param {string} filePath - path to a file
* @returns {string} - path to a file with no .ejs extension
*/
function sanitizePath(filePath) {
if (path.extname(filePath) === '.ejs') {
filePath = filePath.replace(/\.ejs$/, '');
}
return filePath;
}
/**
* Utility function to determine if a file should be rendered
*
* @param {object} config - user input configuration object
* @param {string} filePath - path to a file
* @returns {boolean} - if the specified file should be rendered
*/
function shouldRenderFile(config, filePath) {
if (!config.tech.js && JS_WHITELIST.includes(path.basename(filePath))) {
return false;
}
if (!config.tech.css && CSS_WHITELIST.includes(path.basename(filePath))) {
return false;
}
if (!config.tech.react && REACT_WHITELIST.includes(path.basename(filePath))) {
return false;
}
return true;
}
/**
* Generates a project structure
*
* @param {string} name - slugified package name
* @returns {Promise} - resolves when structure is generated
*/
async function generateStructure(config) {
const filePaths = await read(path.join(__dirname, './template'));
for (const absolutePath of filePaths) {
if (shouldRenderFile(config, absolutePath)) {
const relativePath = path.relative('./template', path.relative(__dirname, absolutePath));
const newPath = sanitizePath(path.join(config.name, relativePath));
const data = await new Promise(resolve => {
ejs.renderFile(absolutePath, config, (e, file) => resolve(file));
});
await fs.outputFile(newPath, data);
}
}
}
/**
* Executes a command at a given path
*
* @param {string} cmd - shell command to execute
* @param {string} cwd - working directory to change to
* @param {boolean} silent - do not send command output to stdout
* @returns {Promise} - resolves when the command completes
*/
function execute(cmd, cwd, silent = true) {
return new Promise(resolve => {
shell.exec(cmd, { cwd, silent }, resolve);
});
}
(async function() {
// Intro logging
console.log(`\n${figlet.textSync('CASTER')}\n`);
try {
// Collect user configuration
const config = sanitizeInput(await collectInput());
console.log('');
// Generate project structure
const generation = generateStructure(config);
ora.promise(generation, { text: 'Generating project structure' });
await generation;
// Install project dependencies
const installation = execute('npm install', config.name);
ora.promise(installation, { text: 'Installing project dependencies' });
await installation;
// Success outro logging
console.log(
`\n ${chalk.bold(chalk.green('Success!'))} Generated ${config.name} at ${path.resolve(config.name)}.`
);
console.log(' The following commands are available within that directory:\n');
config.tech.js && console.log(` ${chalk.cyan('npm run test')} Runs JavaScript unit tests`);
config.tech.js && console.log(` ${chalk.cyan('npm run eslint')} Lints JavaScript files`);
config.tech.css && console.log(` ${chalk.cyan('npm run stylelint')} Lints CSS files`);
console.log(` ${chalk.cyan('npm run prettier')} Format files`);
console.log(chalk.bold('\n Lock and load.\n\n'));
} catch (error) {
// Error outro logging
console.log(`\n ${chalk.bold(chalk.red('Failure.'))} Something went horribly, horribly wrong.\n\n`);
}
})();