@mahjongg/mern-mvc
Version:
A CLI that will build a MERN stack application using create-react-app
362 lines (335 loc) • 11.2 kB
JavaScript
module.exports = (options) => {
const fs = require("fs");
const os = require("os");
const path = require("path");
const {exec} = require("child_process");
const {spawn} = require("superspawn");
const install = require("spawn-npm-install");
const files = options.noPassport ? {
serverString : require(path.join(__dirname, "..", "dataNoPassport", "serverText.js")),
personModelText : require(path.join(__dirname, "..", "dataNoPassport", 'modelText.js')),
routeText : require(path.join(__dirname, "..", "dataNoPassport", 'routeText.js')),
apiText : require(path.join(__dirname, "..", "dataNoPassport", 'apiText.js')),
appText : require(path.join(__dirname, "..", "dataNoPassport", 'appText.js')),
reactIndexText : require(path.join(__dirname, "..", "dataNoPassport", 'reactIndexText.js')),
splashText : require(path.join(__dirname, "..", "dataNoPassport", 'SplashText.js')),
homeText : require(path.join(__dirname, "..", "dataNoPassport", 'HomeText.js'))
} :
{
serverString : require(path.join(__dirname, "..", "data", "serverText.js")),
passportText : require(path.join(__dirname, "..", "data", 'passportText.js')),
userModelText : require(path.join(__dirname, "..", "data", 'modelText.js')),
authText : require(path.join(__dirname, "..", "data", 'authText.js')),
routeText : require(path.join(__dirname, "..", "data", 'routeText.js')),
apiText : require(path.join(__dirname, "..", "data", 'apiText.js')),
appText : require(path.join(__dirname, "..", "data", 'appText.js')),
reactIndexText : require(path.join(__dirname, "..", "data", 'reactIndexText.js')),
signInText : require(path.join(__dirname, "..", "data", 'SignInText.js')),
signUpText : require(path.join(__dirname, "..", "data", 'SignUpText.js')),
homeText : require(path.join(__dirname, "..", "data", 'HomeText.js'))
};
const colors = require(path.join(__dirname,"../constants.js"));
const log = (msg)=>{
console.log(colors.MAGENTA,colors.BRIGHT);
console.log(msg);
console.log(colors.RESET);
};
const createReactApp = (name, options) => {
return new Promise((resolve,reject)=>{
log("Creating React App");
let proc = spawn(
path.join(__dirname,`../node_modules/.bin/create-react-app${os.platform() === 'win32' ? '.cmd' : ''}`),
['client'],{ stdio:'inherit',cwd:`./${name}` }
);
proc.then((stdout)=>{
log("React app created!");
resolve();
}).catch((err)=>{
if(err) throw err;
});
});
};
const addFolders = (name, options) => {
return new Promise((resolve,reject)=>{
log("Creating Folders....");
//make sure the name is a valid npm package name
const reg = /[^a-z0-9\-]/g
if(name.match(reg)){
return reject({message:"Be sure that your folder name contains only lowercase, numbers, or '-'."});
}
let folderList = options.noPassport ?
`controllers models routes scripts` :
`config controllers models routes scripts`;
exec(`mkdir ${name}`,(err,stdout,stderr)=>{
exec(`mkdir ${folderList}`,{cwd:`./${name}`},(err,stdout,stderr)=>{
if(err) reject(err);
log("Done with Folders");
resolve(stdout);
});
});
});
};
const makeServer = (name, options) => {
log("Writing server.js...");
return new Promise((resolve,reject)=>{
fs.writeFile(`./${name}/server.js`,files.serverString(name),function(){
log("Server Written");
resolve();
});
});
};
const installPackages = (name, options) => {
log("Installing Packages...this may take a moment...");
return new Promise((resolve,reject)=>{
exec("npm init --yes",{ cwd: `./${name}` },()=>{
const packageList = options.noPassport ?
["express","express-session","mongoose"] :
["express","express-session","mongoose","passport","passport-local","passport-local-mongoose"];
let packages = install(packageList,
{ stdio: 'inherit',cwd: `./${name}` }
);
packages.on("exit",()=>{
log("Packages Done");
log("Installing Dev-dependencies...");
let dev = install(["jest","concurrently","nodemon"],{saveDev:true,stdio:"inherit",cwd:`./${name}`});
dev.on("exit",()=>{
log("Dev-dependencies Done");
log("Installing React Packages...");
log("Sorry, you won't get a lot of feedback during this step...just be patient");
exec("yarn add react-router-dom axios",{cwd:`./${name}/client`},(err, stdout,stderr)=>{
if(err) reject(err);
console.log(stdout);
log("React Done");
resolve();
});
});
});
});
});
};
const addScripts = (name,options) => {
let count = 0;
log("writing Scripts");
fs.writeFile(`./${name}/scripts/build.js`,
`const args = ["run build"];
const opts = { stdio: "inherit", cwd: "client", shell: true };
require("child_process").spawn("npm", args, opts);
`
,function(){
count++;
if(count === 2){
log("Scripts Done");
}
});
fs.writeFile(`./${name}/scripts/start-client.js`,
`const args = ["start"];
const opts = { stdio: "inherit", cwd: "client", shell: true };
require("child_process").spawn("npm", args, opts);
`
,function(){
count++;
if(count === 2){
log("Scripts Done");
}
});
};
const configPassport = (name,options) =>{
log("Configuring Passport...");
let count = 0;
fs.writeFile(`./${name}/config/passport.js`,files.passportText,function(err, data){
count++;
if(count === 3){
log("Passport Done");
}
});
fs.writeFile(`./${name}/models/index.js`,
`module.exports = {
User:require("./User.js")
}
//add more models as you create them
`,function(err, data){
count++;
if(count === 3){
log("Passport Done");
}
});
fs.writeFile(`./${name}/models/User.js`,files.userModelText,function(err, data){
count++;
if(count === 3){
log("Passport Done");
}
});
};
//this is currently used as a replacement for "config passport" when noPassport = true;
const makeModels = (name, options) => {
log("Writing Models...");
let count = 0;
fs.writeFile(`./${name}/models/index.js`,
`module.exports = {
Person:require("./Person.js")
}
//add more models as you create them`,function(err, data){
count++;
if(count === 2){
log("Models Done");
}
});
fs.writeFile(`./${name}/models/Person.js`,files.personModelText,function(err, data){
count++;
if(count === 2){
log("Models Done");
}
});
};
const makeRoutes = (name,options) =>{
let count = 0;
let max = options.noPassport ? 2 : 3;
log("Adding Routes");
const filePath = `./${name}/routes/`;
fs.writeFile(filePath + "index.js",files.routeText,function(err, data){
count++;
if(count === max){
log("Routes Done");
}
});
if(!options.noPassport) {
fs.writeFile(filePath + "authRoutes.js",files.authText,function(err, data){
count++;
if(count === max){
log("Routes Done");
}
});
}
fs.writeFile(filePath + "apiRoutes.js",files.apiText,function(err, data){
count++;
if(count === max){
log("Routes Done");
}
});
}
const serverLogic = (name, options) =>{
return new Promise((resolve,reject)=>{
let count = 0;
log("Setting Up Enviornment")
//procfile
fs.writeFile(`./${name}/Procfile`,`web: npm run server`,function(err, data){
count++;
if(count === 4){
log("Enviornment Done");
resolve();
}
});
//nodemon.js
fs.writeFile(`./${name}/nodemon.json`,
JSON.stringify({
ignore: ["client/*"]
},null,2),
function(data,err){
count++;
if(count === 4){
log("Enviornment Done");
resolve();
}
});
//edit package.json
fs.readFile(`./${name}/package.json`,"utf8",function(err, data){
const scripts = {
"server": "node server.js",
"client": "node scripts/start-client.js",
"start": "concurrently \"nodemon server.js\" \"npm run client\"",
"build": "node scripts/build.js",
"seed": "node scripts/seedDB.js",
"test": "jest"
};
data = JSON.parse(data);
data.scripts = scripts
data.name = name;
fs.writeFile(`./${name}/package.json`,JSON.stringify(data,null,2),function(err,data){
count++;
if(count === 4){
log("Enviornment Done");
resolve();
}
})
});
//edit react package.json to add proxy
fs.readFile(`./${name}/client/package.json`,"utf8",function(err,data){
data = JSON.parse(data);
data.proxy = "http://localhost:3001/";
fs.writeFile(`./${name}/client/package.json`,JSON.stringify(data,null,2),function(err,data){
count++;
if(count === 4){
log("Enviornment Done");
resolve();
}
})
})
});
}
const addComponents = (name, options) =>{
//add some signin/signup components
const filePath = `./${name}/client/src/components/`;
return new Promise(function(resolve,reject){
log("Creating Components...");
exec("mkdir components",{cwd:`./${name}/client/src`},(err,stdout,stderr)=>{
if(!options.noPassport) {
exec("mkdir Home SignIn SignUp",{cwd:filePath},(err,stdout,stderr)=>{
fs.writeFileSync(filePath + "Home/index.js",files.homeText)
fs.writeFileSync(filePath + "Home/style.css",'')
fs.writeFileSync(filePath + "SignIn/index.js",files.signInText)
fs.writeFileSync(filePath + "SignIn/style.css",'')
fs.writeFileSync(filePath + "SignUp/index.js",files.signUpText)
fs.writeFileSync(filePath + "SignUp/style.css",'')
log("Components Created!");
resolve();
});
} else {
exec("mkdir Home Splash",{cwd:filePath},(err,stdout,stderr)=>{
fs.writeFileSync(filePath + "Home/index.js", files.homeText)
fs.writeFileSync(filePath + "Home/style.css",'')
fs.writeFileSync(filePath + "Splash/index.js", files.splashText)
fs.writeFileSync(filePath + "Splash/style.css",'')
log("Components Created!");
resolve();
});
}
});
});
};
const editApp = (name, options) => {
return new Promise(function(resolve,reject){
log("Adding Those Components To App...");
log("Also removing the Service Worker (you can always put it back if you like)")
let count = 0;
//alter appjs (and others) to add authentication
fs.writeFile(`./${name}/client/src/App.js`,files.appText,function(err,data){
count++;
if(count === 2){
log("Components added to App.js")
resolve();
}
});
// edit index.js so that it doesn not include the service worker
fs.writeFile(`./${name}/client/src/index.js`,files.reactIndexText,function(err,data){
count++;
if(count === 2){
log("Components added to App.js")
resolve();
}
});
});
};
return {
createReactApp,
addFolders,
makeServer,
installPackages,
addScripts,
configPassport,
makeModels,
makeRoutes,
serverLogic,
addComponents,
editApp
};
};