onepoint-new-app
Version:
Easily generate a new fully-equiped React project, optionally with Express & MongoDB.
194 lines (159 loc) • 5.93 kB
JavaScript
// Avoid Node complaining about unhandled rejection errors.
process.on("unhandledRejection", err => console.error(err));
// Node built-in modules.
const path = require("path");
// External modules.
const fs = require("fs-extra");
const chalk = require("chalk");
const cla = require("command-line-args");
// File creators.
const dotEnv = require("./file-creators/dotEnv");
const packageJson = require("./file-creators/packageJson");
const webpackConfig = require("./file-creators/webpackConfig");
const publicIndex = require("./file-creators/publicIndex");
const portValidator = require("./modules/portValidator");
// Custom modules.
const run = require("./modules/run");
const safeToCreateDir = require("./modules/safeToCreateDir");
const adjustPkgJson = require("./modules/adjustPkgJson");
const uuid = require("uuid");
// Other.
const cwd = fs.realpathSync(process.cwd()); // http://bit.ly/2YYe9R8 - because symlinks.
const dir = text => path.resolve(__dirname, text);
// Option definitions.
const optionDefinitions = [
// Main argument.
// Will be validated in the `parseArgs` fxn by the `validate-npm-package-name` pkg.
{ name: "appName", type: String, defaultOption: true },
{
name: "port",
alias: "p",
type: val => portValidator(val, 4000),
defaultValue: 4000
}
];
// Let's go! Push the first dominoe.
letsGo();
async function letsGo() {
let options = cla(optionDefinitions, { partial: true });
console.log("@debug-options", options);
options = {
...options,
appDir: `${cwd}/${options.appName}`,
appId: uuid.v1()
};
createProjectDirectory(options);
createFiles(options);
installDependencies(options);
}
// STEP 3
function createProjectDirectory(options) {
const { appName, appDir } = options;
const greenDir = chalk.green(`${cwd}/`);
const boldName = chalk.green.bold(appName);
safeToCreateDir(options) || process.exit();
console.log(`\nCreating a new app in ${greenDir}${boldName}.`);
// Create the project directory if it doesn't already exist.
fs.mkdirpSync(appDir);
}
// STEP 4
function createFiles(options) {
const { appDir } = options;
// `.env`
fs.writeFileSync(`${appDir}/.env`, dotEnv(options), "utf8");
// `.gitignore`
fs.copySync(dir("files/gitignore.txt"), `${appDir}/.gitignore`);
// `.babelrc`
fs.copySync(dir("files/babelrc.txt"), `${appDir}/.babelrc`);
// `package.json`
fs.writeFileSync(`${appDir}/package.json`, packageJson(options), "utf8");
// `webpack.config.js`
fs.writeFileSync(`${appDir}/webpack.config.js`, webpackConfig({}), "utf8");
const excludedFiles = [".gitkeep"].filter(Boolean);
const filter2 = {
filter: file => excludedFiles.every(f => !file.includes(f))
};
// `src` directory tree.
fs.copySync(dir("./files/src"), `${appDir}/src`, filter2);
fs.writeFileSync(
`${appDir}/src/env.js`,
`export default ${JSON.stringify(options)};`,
"utf8"
);
// `public` directory tree.
fs.mkdirpSync(`${appDir}/public`);
fs.writeFileSync(`${appDir}/public/index.html`, publicIndex(options), "utf8");
}
// STEP 5
async function installDependencies(options) {
const { appName, appDir, server, offline, mongo, force, noInstall } = options;
const forceOffline = offline ? " --offline" : ""; // http://bit.ly/2Z2Ht9c
const cache = offline ? " cache" : "";
// Change into the projects directory.
process.chdir(`${cwd}/${appName}`);
// Install the dependencies.
if (!noInstall) {
offline &&
console.log(`\nIt looks like you're offline or have a bad connection.`);
console.log(`Installing project dependencies via npm${cache}...\n`);
try {
run(`npm i${forceOffline}`);
} catch (e) {
console.log(
`\n${chalk.yellow("An error occurred during the npm installation.")}`
);
// Cleanup what was created *only* if we didn't force install.
if (!force) {
process.chdir(cwd);
run(`rm -rf ${cwd}/${appName}`);
console.log("Created directories and files have been removed.");
process.exit(1);
} else {
console.log("Refusing to remove created directories and files");
console.log("since they were created in a pre-existing location:");
console.log(` ${chalk.cyan(cwd)}`);
}
}
}
// Adjust the package.json dependencies to show their installed version.
// E.x. - "react": "^16" => "react": "^16.6.1"
!noInstall && adjustPkgJson(appDir);
// Initialize git.
try {
run("git init", true); // Don't display stdout.
console.log("Initialized a git repository.\n");
} catch (e) {
console.log(
`Tried to initialize a new ${chalk.bold("git")} repository but couldn't.`
);
console.log(`Do you have have ${chalk.bold("git")} installed?\n`);
}
noInstall &&
console.log(
"No dependecies intalled. `package.json` will not reflect specific versions."
);
// Display the final message.
const cyanDir = chalk.cyan(appDir);
const boldName = chalk.bold(appName);
const serverMsg = server ? "and Express servers" : "server";
console.log(`\nSuccess! Created ${boldName} at ${cyanDir}.`);
console.log(`Inside that directory you can run several commands:\n`);
console.log(` ${chalk.cyan("npm start")}`);
console.log(` Starts the development ${serverMsg}.\n`);
console.log(` ${chalk.cyan("npm run build")}`);
console.log(` Bundles the app into static files for production.\n`);
if (server) {
console.log(` ${chalk.cyan("npm run local")}`);
console.log(
` Starts only the Express server (no development server).\n`
);
}
if (mongo) {
console.log("To see tips & tasks related to MongoDB in production:");
console.log(` ${chalk.cyan("cna --mongoHelp")}\n`);
}
console.log(`\nGet started by typing:\n`);
console.log(` ${chalk.cyan("cd")} ${appName}`);
console.log(` ${chalk.cyan("npm start")}\n`);
}