Why is Heroku unable to cd to my frontend React App? - node.js

I keep getting this error message when I git push my backend to heroku:
remote: -----> Build
remote: Running heroku-postbuild
remote:
remote: > mp-backend#1.0.0 heroku-postbuild /tmp/build_eaac46c4d4704cb2a35221c2068a754a
remote: > cd map-project && npm install --only=dev && npm install && npm run build
remote:
remote: sh: 1: cd: can't cd to map-project
remote: npm ERR! code ELIFECYCLE
remote: npm ERR! errno 2
remote: npm ERR! mp-backend#1.0.0 heroku-postbuild: `cd map-project && npm install --only=dev && npm install && npm run build`
remote: npm ERR! Exit status 2
remote: npm ERR!
remote: npm ERR! Failed at the mp-backend#1.0.0 heroku-postbuild script.
Leading to a ! [remote rejected] master -> master (pre-receive hook declined).
I do not understand. I added this:
"scripts": {
"start": "node index.js",
"heroku-postbuild": "cd map-project && npm install --only=dev && npm install && npm run build"
}
to the package.json for my backend and then ran
git init
git add .
git commit -m "Initial commit"
heroku create
git push heroku master
while cd'd to my backend. What keeps causing my build to fail?
additional code that may help:
//my backend under folder mp-backend
const express = require('express');
const path = require('path');
const app = express();
const populartimes = require('populartimes.js');
//Node Geocoder
const NodeGeocoder = require('node-geocoder');
const options = {
provider: 'google',
apiKey:
};
const geocoder = NodeGeocoder(options);
const getWeekDay = () => {
typical getDay codes
}
const unRendered = 'https://i.imgur.com/fgSeO4a.png';
const Rendered = 'https://i.imgur.com/sg9a1sG.png';
//Markers
const markers = [an array of marker objects]
const findLatLong = async function findLatLong(){
for(let i = 0; i < markers.length; i++){
const temp = await geocoder.geocode(markers[i].address)
markers[i].position.lat = temp[0].latitude;
markers[i].position.lng = temp[0].longitude;
}
}
const fillNewMarker = async function fillNewMarker(num){
const i = num;
let temp;
let data;
await populartimes(markers[i].placeID)
.then(out => {data = out; temp = 'Currently ' + data.now.currently + ' full.'})
.catch(() => {temp = 'There is currently no data available.'});
markers[i].busy = temp;
}
// Serve the static files from the React app
app.use(express.static(path.join(__dirname, 'map-project/build')));
var bodyParser = require('body-parser');
app.use(bodyParser.json()); // support json encoded bodies
app.use(bodyParser.urlencoded({ extended: true })); // support encoded bodies
app.get('/api/getMarkers', async (req,res) => {
await findLatLong();
console.log('Find LatLng Complete')
//await fillPopularity();
//console.log('Find Business Complete')
var mark = markers;
res.json(mark);
console.log('Sent Markers');
console.log(markers);
});
app.post('/api/newMarker', async (req,res) => {
console.log('Request Recieved');
const n = req.body.index;
console.log(n);
await fillNewMarker(n);
console.log('Retrieve Data For New Marker Complete');
markers[n].icon = Rendered;
var mark = markers;
res.json(mark);
console.log('Sent Markers');
console.log(markers);
})
app.get('*', (req,res) =>{
res.sendFile(path.join(__dirname+'/map-project/build/index.html'));
});
const port = process.env.PORT || 5000;
app.listen(port);
console.log('App is listening on port ' + port);

What's your folder structure? What does your project look like?
You haven't provided enough information for anyone to help you.
Edit:
You have two options here, mono-repo and poly-repo. Basically, do you want your front and back ends always tied together (mono-repo) or do you want them to be entirely separate things (poly-repo)? While I am a huge fan of mono-repos, even with large scopes, don't let me necessarily persuade you either way without doing your own research. I think a mono-repo would be better for you in this situation, as they are less work to manage.
Here's an example of a folder structure for your project:
my-project
----.git
----packages
--------back-end
--------front-end
----README.md
----(any other whole-project type files)
Your whole project will be under one Git repository. See your issue was that Git only tracks the folder you're in and down. So if you put Git in packages/backend, it'll only find and track that folder's children. Meaning when you push, anything not in packages/backend just doesn't exist.
This solution I propose has you tie the two packages quite closely. When you push to git or heroku, both your front and back end will be together. This also lets you handle some other tooling better, like say documentation. You can add instructions for deploying the system as a whole, rather than an individual part.

Related

Why does claudia.js fail package validation when using nconf in a specific way?

I am using nconf in a NodeJS project, which I want to deploy to AWS Lambda using claudia.js. I have followed this example:
const path = require('path');
const nconf = require('nconf');
function Configuration(){
nconf.argv().env({ lowerCase: true, separator: '_' });
let defaultJsonPath = path.join(__dirname, 'default.json');
console.log(`Using default config at ${defaultJsonPath}`);
nconf.file("default", defaultJsonPath);
}
Configuration.prototype.get = function(key) {
return nconf.get(key);
};
module.exports = new Configuration();
This code works perfectly fine when testing on my local machine using claudia-local-api.
However, when I try to deploy to Lambda using claudia create .. it simply stops at validating package without any errors.
claudia create --verbose --profile wgmtest --version dev --region eu-west-1 --api-module app
packaging files npm install -q --no-audit --production
npm WARN claudia#1.0.0 No description
npm WARN claudia#1.0.0 No repository field.
added 21 packages from 18 contributors in 0.339s
2 packages are looking for funding
run `npm fund` for details
validating package
5.12.0
If I export a class instead of a function, the claudia create command works fine (see below). I'd like to understand what the issue is here, or at least how I can get some additional log output of the problem.
const path = require('path');
const nconf = require('nconf');
class Configuration {
constructor() {
nconf.argv().env({ lowerCase: true, separator: '_' });
let defaultJsonPath = path.join(__dirname, 'default.json');
console.log(`Using default config at ${defaultJsonPath}`);
nconf.file("default", defaultJsonPath);
}
static get(key) {
return nconf.get(key);
}
}
module.exports = Configuration;

When collection is created, the documents are added to it at once

Still learning firestore, i want to write a function which would create multiple documents upon the creation of a collection at once. So i wrote this code to try it out. i saw the code as an answer to something similar.
const fsRef = admin.firestore();
export const moreCreations = functions.firestore
.document(
"dev_env/schools/school_collections/KithAndKin7394/students/{userID}"
)
.onCreate((snap, context) => {
const newSchoolRef = fsRef
.collection("dev_env")
.doc("schools")
.collection("school_collections")
.doc("KithAndKin7394")
.collection("students")
.doc(snap.id);
// Trying something on documents
const documentIds = [
'CRK_IRK',
'PHE',
'agricScience',
'basicScience',
'basicTechnology',
'businessStudies',
'computerStudies',
'creativeArts',
'english',
'frenchLanguage',
'hausaLanguage',
'homeEconomics',
'iboLanguage',
'maths',
'socialStudies',
'yoruba'
];
const batch = fsRef.batch();
const data ={};
const setbatch = documentIds.forEach(docId => {
batch.set(newSchoolRef.collection('JSS1').doc('${docId}'), data);
})
batch. commit().then(response => {
console.log('Success');
}).catch(err => {
console.error(err);
})
});
I am getting these errors:
i deploying functions
Running command: npm --prefix "$RESOURCE_DIR" run lint
> functions# lint C:\brighterbrains\functions
> tslint --project tsconfig.json
ERROR: C:/brighterbrains/functions/src/index.ts:168:23 - Expression has type `void`. Put it on its own line as a statement.
npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! functions# lint: `tslint --project tsconfig.json`
npm ERR! Exit status 2
npm ERR!
npm ERR! Failed at the functions# lint script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.
npm ERR! A complete log of this run can be found in:
npm ERR! C:\Users\dell\AppData\Roaming\npm-cache\_logs\2020-03-30T02_04_34_455Z-debug.log
Error: functions predeploy error: Command terminated with non-zero exit code2
What i want is when the collection "JSS1" is created, the documents "documentsId" are added to it at once. The code is from this forum but it is not working. Please can anyone point my mistake and help with me with the corrections? The documentation doesn't do or say anything about such actions.
Thanks in advance.
Remove const setbatch = from before the forEach(). It doesn't have a return value.
I have solved it. Below is the updated code:
export const moreCreations = functions.firestore
.document(
"dev_env/schools/school_collections/KithAndKin7394/students/{userID}"
)
.onCreate((snap, context) => {
const newSchoolRef = fsRef
.collection("dev_env")
.doc("schools")
.collection("school_collections")
.doc("KithAndKin7394")
.collection("students")
.doc(snap.id);
// Trying something on documents
const documentIds = [
'CRK_IRK',
'PHE',
'agricScience',
'basicScience',
'basicTechnology',
'businessStudies',
'computerStudies',
'creativeArts',
'english',
'frenchLanguage',
'hausaLanguage',
'homeEconomics',
'iboLanguage',
'maths',
'socialStudies',
'yoruba'
];
const batch = fsRef.batch();
const data ={};
documentIds.forEach(docId => {
batch.set(newSchoolRef.collection('JSS1').doc(docId), data);
})
batch.commit().then(response => {
console.log('Success');
}).catch(err => {
console.error(err);
})
});
It deployed successfully and upon creation of a document, the collection JSS1 is automatically created with the documents under the documentsId array automatically added.
I wanna thank #samthecodingman for helping me spot the area i need to edit. And #DougStevenson too.

eslint throws error

i am new android beginner and try to deploy firebase functions but show some error how to solve this problem plz help me.
Firebase database structure
User Table
Users
user_id
device_token : user_device_token
Name : user_name
Notification Table
notifications
to_user_id
notification_id
from : from_user_id
Error
Index.js
'use strict'
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
/*
* 'OnWrite' works as 'addValueEventListener' for android. It will fire the function
* everytime there is some item added, removed or changed from the provided 'database.ref'
* 'sendNotification' is the name of the function, which can be changed according to
* your requirement
*/
exports.sendNotification = functions.database.ref('/notifications/{user_id}/{notification_id}').onWrite(event => {
/*
* You can store values as variables from the 'database.ref'
* Just like here, I've done for 'user_id' and 'notification'
*/
const user_id = event.params.user_id;
const notification_id = event.params.notification_id;
console.log('We have a notification from : ', user_id);
/*
* Stops proceeding to the rest of the function if the entry is deleted from database.
* If you want to work with what should happen when an entry is deleted, you can replace the
* line from "return console.log.... "
*/
if(!event.data.val()){
return console.log('A Notification has been deleted from the database : ', notification_id);
}
/*
* 'fromUser' query retreives the ID of the user who sent the notification
*/
const fromUser = admin.database().ref(`/notifications/${user_id}/${notification_id}`).once('value');
return fromUser.then(fromUserResult => {
const from_user_id = fromUserResult.val().from;
console.log('You have new notification from : ', from_user_id);
/*
* The we run two queries at a time using Firebase 'Promise'.
* One to get the name of the user who sent the notification
* another one to get the devicetoken to the device we want to send notification to
*/
const userQuery = admin.database().ref(`/Users/${from_user_id}/Name`).once('value');
const deviceToken = admin.database().ref(`/Users/${user_id}/device_token`).once('value');
return Promise.all([userQuery, deviceToken]).then(result => {
const userName = result[0].val();
const token_id = result[1].val();
/*
* We are creating a 'payload' to create a notification to be sent.
*/
const payload = {
notification: {
title : "New Friend Request",
body: `${userName} has sent you request`,
icon: "default",
}
};
/*
* Then using admin.messaging() we are sending the payload notification to the token_id of
* the device we retreived.
*/
return admin.messaging().sendToDevice(token_id, payload).then(response => {
console.log('This was the notification Feature');
});
});
});
});
**cmd show error **
C:\Users\TahirAliAwan\Desktop\Function>firebase deploy
=== Deploying to 'videochat-96f75'...
i deploying functions
Running command: npm --prefix %RESOURCE_DIR% run lint
functions# lint C:\Users\TahirAliAwan\Desktop\Function\functions
eslint .
C:\Users\TahirAliAwan\Desktop\Function\functions\index.js
61:11 warning Avoid nesting promises promise/no-nesting
84:14 warning Avoid nesting promises promise/no-nesting
84:69 error Each then() should return a value or throw promise/always-return
✖ 3 problems (1 error, 2 warnings)
npm ERR! Windows_NT 10.0.10586
npm ERR! argv "C:\Program Files\nodejs\node.exe" "C:\Program Files\nodejs\node_modules\npm\bin\npm-cli.js" "--prefix" "C:\Users\TahirAliAwan\Desktop\Function\functions" "run" "lint"
npm ERR! node v6.11.5
npm ERR! npm v3.10.10
npm ERR! code ELIFECYCLE
npm ERR! functions# lint: eslint .
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the functions# lint script 'eslint .'.
npm ERR! Make sure you have the latest version of node.js and npm installed.
npm ERR! If you do, this is most likely a problem with the functions package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR! eslint .
npm ERR! You can get information on how to open an issue for this project with:
npm ERR! npm bugs functions
npm ERR! Or if that isn't available, you can get their info via:
npm ERR! npm owner ls functions
npm ERR! There is likely additional logging output above.
npm ERR! Please include the following file with any support request:
npm ERR! C:\Users\TahirAliAwan\Desktop\Function\npm-debug.log
Error: functions predeploy error: Command terminated with non-zero exit code1
After #PeterHaddad tip and my effort, I solved this problem by removing this line
.then(response => { console.log('This was the notification Feature'); and update nodejs.
Thanks to all.

npm install from the parent directory

I have a directory structure like this: /a/b/c
Directory c contains package.json and should contain node_modules.
How can I execute npm install from inside directory a?
I tried this way: npm --prefix b/c install b/c but this way, all the symlinks are created directly inside c instead of the default node_modules/.bin.
Is there any way to achieve that?
node: 6.2.2
npm: 3.10.2
Using an npm pre install hook in a package.json within your a directory is likely the best choice in this situation.
scripts: {
preinstall: `cd b/c && npm install`
}
This way running npm install in directory a will also do the c directory install and provide a seamless dev experience.
A bit overkill but may be useful...
With the help of recursion you can find node_modules.
you could run this file in a parent directory to find node_modules in child directory and pass npm arguments.
Note: tested on Windows
var child_process = require('child_process');
var fs = require('fs');
var path = require('path');
var safe = 0;
let args = process.argv.splice(2).toString().replace(/,/g ,' ');
function recurse(_path){
safe ++;
if(safe > 5000){
console.log('directory may be too large')
return
}
if(/node_modules$/.test(_path)){
let cwd = path.resolve(__dirname ,_path)
console.log('found node_modules at '+cwd)
child_process.exec(`start cmd.exe /k npm ${args}`,{cwd})
return
}
let directoryList = fs.readdirSync(_path);
directoryList.forEach(function(nextDir){
if(fs.statSync(_path+'/'+nextDir).isFile()){
return
}
if(/^\./.test(nextDir)){ //.folder beginging with .
return
}
recurse(_path+'/'+nextDir);
})
}
recurse('./' )

The best way to run npm install for nested folders?

What is the most correct way to install npm packages in nested sub folders?
my-app
/my-sub-module
package.json
package.json
What is the best way to have packages in /my-sub-module be installed automatically when npm install run in my-app?
I prefer using post-install, if you know the names of the nested subdir. In package.json:
"scripts": {
"postinstall": "cd nested_dir && npm install",
...
}
Per #Scott's answer, the install|postinstall script is the simplest way as long as sub-directory names are known. This is how I run it for multiple sub dirs. For example, pretend we have api/, web/ and shared/ sub-projects in a monorepo root:
// In monorepo root package.json
{
...
"scripts": {
"postinstall": "(cd api && npm install); (cd web && npm install); (cd shared && npm install)"
},
}
On Windows, replace the ; between the parentesis with &&.
// In monorepo root package.json
{
...
"scripts": {
"postinstall": "(cd api && npm install) && (cd web && npm install) && (cd shared && npm install)"
},
}
Use Case 1: If you want be able to run npm commands from within each subdirectory (where each package.json is), you will need to use postinstall.
As I often use npm-run-all anyway, I use it to keep it nice and short (the part in the postinstall):
{
"install:demo": "cd projects/demo && npm install",
"install:design": "cd projects/design && npm install",
"install:utils": "cd projects/utils && npm install",
"postinstall": "run-p install:*"
}
This has the added benefit that I can install all at once, or individually. If you don't need this or don't want npm-run-all as a dependency, check out demisx's answer (using subshells in postinstall).
Use Case 2: If you will be running all npm commands from the root directory (and, for example, won't be using npm scripts in subdirectories), you could simply install each subdirectory like you would any dependecy:
npm install path/to/any/directory/with/a/package-json
In the latter case, don't be surprised that you don't find any node_modules or package-lock.json file in the sub-directories - all packages will be installed in the root node_modules, which is why you won't be able to run your npm commands (that require dependencies) from any of your subdirectories.
If you're not sure, use case 1 always works.
If you want to run a single command to install npm packages in nested subfolders, you can run a script via npm and main package.json in your root directory. The script will visit every subdirectory and run npm install.
Below is a .js script that will achieve the desired result:
var fs = require('fs');
var resolve = require('path').resolve;
var join = require('path').join;
var cp = require('child_process');
var os = require('os');
// get library path
var lib = resolve(__dirname, '../lib/');
fs.readdirSync(lib).forEach(function(mod) {
var modPath = join(lib, mod);
// ensure path has package.json
if (!fs.existsSync(join(modPath, 'package.json'))) {
return;
}
// npm binary based on OS
var npmCmd = os.platform().startsWith('win') ? 'npm.cmd' : 'npm';
// install folder
cp.spawn(npmCmd, ['i'], {
env: process.env,
cwd: modPath,
stdio: 'inherit'
});
})
Note that this is an example taken from a StrongLoop article that specifically addresses a modular node.js project structure (including nested components and package.json files).
As suggested, you could also achieve the same thing with a bash script.
EDIT: Made the code work in Windows
Just for reference in case people come across this question. You can now:
Add a package.json to a subfolder
Install this subfolder as reference-link in the main package.json:
npm install --save path/to/my/subfolder
The accepted answer works, but you can use --prefix to run npm commands in a selected location.
"postinstall": "npm --prefix ./nested_dir install"
And --prefix works for any npm command, not just install.
You can also view the current prefix with
npm prefix
And set your global install (-g) folder with
npm config set prefix "folder_path"
Maybe TMI, but you get the idea...
My solution is very similar.
Pure Node.js
The following script examines all subfolders (recursively) as long as they have package.json and runs npm install in each of them.
One can add exceptions to it: folders allowed not having package.json. In the example below one such folder is "packages".
One can run it as a "preinstall" script.
const path = require('path')
const fs = require('fs')
const child_process = require('child_process')
const root = process.cwd()
npm_install_recursive(root)
// Since this script is intended to be run as a "preinstall" command,
// it will do `npm install` automatically inside the root folder in the end.
console.log('===================================================================')
console.log(`Performing "npm install" inside root folder`)
console.log('===================================================================')
// Recurses into a folder
function npm_install_recursive(folder)
{
const has_package_json = fs.existsSync(path.join(folder, 'package.json'))
// Abort if there's no `package.json` in this folder and it's not a "packages" folder
if (!has_package_json && path.basename(folder) !== 'packages')
{
return
}
// If there is `package.json` in this folder then perform `npm install`.
//
// Since this script is intended to be run as a "preinstall" command,
// skip the root folder, because it will be `npm install`ed in the end.
// Hence the `folder !== root` condition.
//
if (has_package_json && folder !== root)
{
console.log('===================================================================')
console.log(`Performing "npm install" inside ${folder === root ? 'root folder' : './' + path.relative(root, folder)}`)
console.log('===================================================================')
npm_install(folder)
}
// Recurse into subfolders
for (let subfolder of subfolders(folder))
{
npm_install_recursive(subfolder)
}
}
// Performs `npm install`
function npm_install(where)
{
child_process.execSync('npm install', { cwd: where, env: process.env, stdio: 'inherit' })
}
// Lists subfolders in a folder
function subfolders(folder)
{
return fs.readdirSync(folder)
.filter(subfolder => fs.statSync(path.join(folder, subfolder)).isDirectory())
.filter(subfolder => subfolder !== 'node_modules' && subfolder[0] !== '.')
.map(subfolder => path.join(folder, subfolder))
}
If you have find utility on your system, you could try running the following command in your application root directory:
find . ! -path "*/node_modules/*" -name "package.json" -execdir npm install \;
Basically, find all package.json files and run npm install in that directory, skipping all node_modules directories.
EDIT As mentioned by fgblomqvist in comments, npm now supports workspaces too.
Some of the answers are quite old. I think nowadays we have some new options available to setup monorepos.
I would suggest using yarn workspaces:
Workspaces are a new way to set up your package architecture that’s available by default starting from Yarn 1.0. It allows you to setup multiple packages in such a way that you only need to run yarn install once to install all of them in a single pass.
If you prefer or have to stay with npm, I suggest taking a look at lerna:
Lerna is a tool that optimizes the workflow around managing multi-package repositories with git and npm.
lerna works perfect with yarn workspaces too - article. I've just finished setting up a monorepo project - example.
And here is an example of a multi-package project configured to use npm + lerna - MDC Web: they run lerna bootstrap using package.json's postinstall.
Adding Windows support to snozza's answer, as well as skipping of node_modules folder if present.
var fs = require('fs')
var resolve = require('path').resolve
var join = require('path').join
var cp = require('child_process')
// get library path
var lib = resolve(__dirname, '../lib/')
fs.readdirSync(lib)
.forEach(function (mod) {
var modPath = join(lib, mod)
// ensure path has package.json
if (!mod === 'node_modules' && !fs.existsSync(join(modPath, 'package.json'))) return
// Determine OS and set command accordingly
const cmd = /^win/.test(process.platform) ? 'npm.cmd' : 'npm';
// install folder
cp.spawn(cmd, ['i'], { env: process.env, cwd: modPath, stdio: 'inherit' })
})
Inspired by the scripts provided here, I built a configurable example which:
can be setup to use yarn or npm
can be setup to determine the command to use based on lock files so that if you set it to use yarn but a directory only has a package-lock.json it will use npm for that directory (defaults to true).
configure logging
runs installations in parallel using cp.spawn
can do dry runs to let you see what it would do first
can be run as a function or auto run using env vars
when run as a function, optionally provide array of directories to check
returns a promise that resolves when completed
allows setting max depth to look if needed
knows to stop recursing if it finds a folder with yarn workspaces (configurable)
allows skipping directories using a comma separated env var or by passing the config an array of strings to match against or a function which receives the file name, file path, and the fs.Dirent obj and expects a boolean result.
const path = require('path');
const { promises: fs } = require('fs');
const cp = require('child_process');
// if you want to have it automatically run based upon
// process.cwd()
const AUTO_RUN = Boolean(process.env.RI_AUTO_RUN);
/**
* Creates a config object from environment variables which can then be
* overriden if executing via its exported function (config as second arg)
*/
const getConfig = (config = {}) => ({
// we want to use yarn by default but RI_USE_YARN=false will
// use npm instead
useYarn: process.env.RI_USE_YARN !== 'false',
// should we handle yarn workspaces? if this is true (default)
// then we will stop recursing if a package.json has the "workspaces"
// property and we will allow `yarn` to do its thing.
yarnWorkspaces: process.env.RI_YARN_WORKSPACES !== 'false',
// if truthy, will run extra checks to see if there is a package-lock.json
// or yarn.lock file in a given directory and use that installer if so.
detectLockFiles: process.env.RI_DETECT_LOCK_FILES !== 'false',
// what kind of logging should be done on the spawned processes?
// if this exists and it is not errors it will log everything
// otherwise it will only log stderr and spawn errors
log: process.env.RI_LOG || 'errors',
// max depth to recurse?
maxDepth: process.env.RI_MAX_DEPTH || Infinity,
// do not install at the root directory?
ignoreRoot: Boolean(process.env.RI_IGNORE_ROOT),
// an array (or comma separated string for env var) of directories
// to skip while recursing. if array, can pass functions which
// return a boolean after receiving the dir path and fs.Dirent args
// #see https://nodejs.org/api/fs.html#fs_class_fs_dirent
skipDirectories: process.env.RI_SKIP_DIRS
? process.env.RI_SKIP_DIRS.split(',').map(str => str.trim())
: undefined,
// just run through and log the actions that would be taken?
dry: Boolean(process.env.RI_DRY_RUN),
...config
});
function handleSpawnedProcess(dir, log, proc) {
return new Promise((resolve, reject) => {
proc.on('error', error => {
console.log(`
----------------
[RI] | [ERROR] | Failed to Spawn Process
- Path: ${dir}
- Reason: ${error.message}
----------------
`);
reject(error);
});
if (log) {
proc.stderr.on('data', data => {
console.error(`[RI] | [${dir}] | ${data}`);
});
}
if (log && log !== 'errors') {
proc.stdout.on('data', data => {
console.log(`[RI] | [${dir}] | ${data}`);
});
}
proc.on('close', code => {
if (log && log !== 'errors') {
console.log(`
----------------
[RI] | [COMPLETE] | Spawned Process Closed
- Path: ${dir}
- Code: ${code}
----------------
`);
}
if (code === 0) {
resolve();
} else {
reject(
new Error(
`[RI] | [ERROR] | [${dir}] | failed to install with exit code ${code}`
)
);
}
});
});
}
async function recurseDirectory(rootDir, config) {
const {
useYarn,
yarnWorkspaces,
detectLockFiles,
log,
maxDepth,
ignoreRoot,
skipDirectories,
dry
} = config;
const installPromises = [];
function install(cmd, folder, relativeDir) {
const proc = cp.spawn(cmd, ['install'], {
cwd: folder,
env: process.env
});
installPromises.push(handleSpawnedProcess(relativeDir, log, proc));
}
function shouldSkipFile(filePath, file) {
if (!file.isDirectory() || file.name === 'node_modules') {
return true;
}
if (!skipDirectories) {
return false;
}
return skipDirectories.some(check =>
typeof check === 'function' ? check(filePath, file) : check === file.name
);
}
async function getInstallCommand(folder) {
let cmd = useYarn ? 'yarn' : 'npm';
if (detectLockFiles) {
const [hasYarnLock, hasPackageLock] = await Promise.all([
fs
.readFile(path.join(folder, 'yarn.lock'))
.then(() => true)
.catch(() => false),
fs
.readFile(path.join(folder, 'package-lock.json'))
.then(() => true)
.catch(() => false)
]);
if (cmd === 'yarn' && !hasYarnLock && hasPackageLock) {
cmd = 'npm';
} else if (cmd === 'npm' && !hasPackageLock && hasYarnLock) {
cmd = 'yarn';
}
}
return cmd;
}
async function installRecursively(folder, depth = 0) {
if (dry || (log && log !== 'errors')) {
console.log('[RI] | Check Directory --> ', folder);
}
let pkg;
if (folder !== rootDir || !ignoreRoot) {
try {
// Check if package.json exists, if it doesnt this will error and move on
pkg = JSON.parse(await fs.readFile(path.join(folder, 'package.json')));
// get the command that we should use. if lock checking is enabled it will
// also determine what installer to use based on the available lock files
const cmd = await getInstallCommand(folder);
const relativeDir = `${path.basename(rootDir)} -> ./${path.relative(
rootDir,
folder
)}`;
if (dry || (log && log !== 'errors')) {
console.log(
`[RI] | Performing (${cmd} install) at path "${relativeDir}"`
);
}
if (!dry) {
install(cmd, folder, relativeDir);
}
} catch {
// do nothing when error caught as it simply indicates package.json likely doesnt
// exist.
}
}
if (
depth >= maxDepth ||
(pkg && useYarn && yarnWorkspaces && pkg.workspaces)
) {
// if we have reached maxDepth or if our package.json in the current directory
// contains yarn workspaces then we use yarn for installing then this is the last
// directory we will attempt to install.
return;
}
const files = await fs.readdir(folder, { withFileTypes: true });
return Promise.all(
files.map(file => {
const filePath = path.join(folder, file.name);
return shouldSkipFile(filePath, file)
? undefined
: installRecursively(filePath, depth + 1);
})
);
}
await installRecursively(rootDir);
await Promise.all(installPromises);
}
async function startRecursiveInstall(directories, _config) {
const config = getConfig(_config);
const promise = Array.isArray(directories)
? Promise.all(directories.map(rootDir => recurseDirectory(rootDir, config)))
: recurseDirectory(directories, config);
await promise;
}
if (AUTO_RUN) {
startRecursiveInstall(process.cwd());
}
module.exports = startRecursiveInstall;
And with it being used:
const installRecursively = require('./recursive-install');
installRecursively(process.cwd(), { dry: true })
find . -maxdepth 1 -type d \( ! -name . \) -exec bash -c "cd '{}' && npm install" \;
[For macOS, Linux users]:
I created a bash file to install all dependencies in the project and nested folder.
find . -name node_modules -prune -o -name package.json -execdir npm install \;
Explain: In the root directory, exclude the node_modules folder (even inside nested folders), find the directory that has the package.json file then run the npm install command.
In case you just want to find on specified folders (eg: abc123, def456 folder), run as below:
find ./abc123/* ./def456/* -name node_modules -prune -o -name package.json -execdir npm install \;
To run npm install on every subdirectory you can do something like:
"scripts": {
...
"install:all": "for D in */; do npm install --cwd \"${D}\"; done"
}
where
install:all is just the name of the script, you can name it whatever you please
D Is the name of the directory at the current iteration
*/ Specifies where you want to look for subdirectories. directory/*/ will list all directories inside directory/ and directory/*/*/ will list all directories two levels in.
npm install -cwd install all dependencies in the given folder
You could also run several commands, for example:
for D in */; do echo \"Installing stuff on ${D}\" && npm install --cwd \"${D}\"; done
will print "Installing stuff on your_subfolder/" on every iteration.
This works for yarn too
Any language that can get a list of directories and run shell commands can do this for you.
I know it isn't the answer OP was going for exactly, but it's one that will always work. You need to create an array of subdirectory names, then loop over them and run npm i, or whatever command you're needing to run.
For reference, I tried npm i **/, which just installed the modules from all the subdirectories in the parent. It's unintuitive as hell, but needless to say it's not the solution you need.

Resources