0

i am trying to make a script that can automate my projects every time i create a new react application so first I've made a bat file that runs my main.js script

here is the bat file

@echo off
setlocal enableextensions

REM run my main
node "C:\Windows\System32\automate\main.js" %1

and here is the main.js file

const { exec } = require("child_process");
const { replace, rename } = require("./file.js");
const axios = require("axios").default;

let state = {
  cwd: "E:/WORK/Projects",
  token: "[[my github token]]",
  project: {
    name: process.argv[2],
    path: "E:/WORK/Projects/" + process.argv[2],
    src: "E:/WORK/Projects/" + process.argv[2] + "/src",
  },
};

// init
let { cwd, project, token } = state;

//
//
// main
// create the app
exec("create-react-app " + project.name, { cwd });

// install debs
exec("npm i node-sass", { cwd: project.path });

// use sass
rename(project.src + "/index.css", "index.scss");
rename(project.src + "/App.css", "App.scss");
replace(project.src + "/index.js", "./index.css", "./index.scss");
replace(project.src + "/App.js", "./App.css", "./App.scss");

// structure my app
let code = [
  'mkdir "' + project.src + '/App"',
  'mkdir "' + project.src + '/App/Elements"',
  'mkdir "' + project.src + '/App/nav"',
  'touch "' + project.src + '/App/nav.jsx"',
  'touch "' + project.src + '/App/nav/nav.scss"',
];
code = code.join(" && ");
exec(code);

// push to github
axios({
  method: "post",
  url: "https://api.github.com/user/repos?access_token=" + token,
  data: {
    name: project.name,
  },
})
  .then((res) => {
    let repo = res.data.clone_url;
    let c = [
      "git remote add origin " + repo,
      "git push --set-upstream origin master",
      "git push",
    ].join(" && ");
    exec(c);
  })
  .catch((err) => {
    console.log(err.response.data.errors);
  });

// open in code
exec("code " + project.path);

// final message
console.log("Have Fun!");

now everything is ready but the only problem i have that every line gets executed asynchronously
example like create-react-app command this takes a lot of time and every next line depends on it to finish first

abdelhamied mostafa
  • 215
  • 1
  • 3
  • 14
  • 1
    Use the callback argument as noted in the docs. https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback I believe you will have to convert it to a promise so that you don't get callback hell, unless the api already supports promises. Then you can use async await so that your code doesn't need to change much and it can appear to run line by line. – George Apr 05 '20 at 13:17
  • 1
    It executes line by line you just dont wait for the external process to finish before executing next process – Estradiaz Apr 05 '20 at 13:24
  • oooh i got it, the problem in exec itself – abdelhamied mostafa Apr 05 '20 at 13:25

3 Answers3

2

You can use execSync instead of exec:

const {execSync} = require('child_process');

Here is the main documentation about this feature: NodeJs Docs

Specific Description:

The child_process.execSync() method is generally identical to child_process.exec() with the exception that the method will not return until the child process has fully closed. When a timeout has been encountered and killSignal is sent, the method won't return until the process has completely exited. If the child process intercepts and handles the SIGTERM signal and doesn't exit, the parent process will wait until the child process has exited.

Hirad Nikoo
  • 1,599
  • 16
  • 26
1

This is a classic case where you can use locks or semaphores.

Just lock the execution before the desired line and remove it after that.

refer this for locks in js - How to implement a lock in JavaScript

vbhv
  • 31
  • 7
  • i remember this from the class but can it increase the performance than execSync method? – abdelhamied mostafa Apr 05 '20 at 13:43
  • 1
    If you have a node server that is serving requests, then it's better to use async methods so your script isn't blocking every other process while it's working, but if you're just running a script to set a project up like a command line tool it makes sense to just use the sync version. – George Apr 05 '20 at 13:55
0

You can use promisify from the util module to turn the async exec function into a promise so you can then use await exec in order for it to appear as executing synchronously.

const util = require('util');
const exec = util.promisify(require('child_process').exec);

Then you can wrap your code in a function like this:

async function init(){
  //your code
  await exec("create-react-app " + project.name, { cwd });
  //...
}
init();

execSync may actually be easier for a simple script like this, but you may find the async versions better in other cases.

George
  • 2,330
  • 3
  • 15
  • 36