├── .travis.yml ├── test ├── test-package-fail.json ├── test-package.json └── index.js ├── .gitignore ├── README.md ├── package.json └── recursive-install.js /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "6" 5 | - "5" 6 | - "4" 7 | - "iojs" 8 | - "0.12" 9 | -------------------------------------------------------------------------------- /test/test-package-fail.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample-app", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "left-pad": "^error&6" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | *.xcf 10 | *.db 11 | .env 12 | 13 | build 14 | node_modules 15 | npm-debug.log 16 | index.min.js 17 | lb-services.js 18 | -------------------------------------------------------------------------------- /test/test-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sample-app", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "left-pad": "^1.1.0" 6 | }, 7 | "devDependencies": { 8 | "right-pad": "^1.0.1" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | recursive-install [![Build Status](https://travis-ci.org/emgeee/recursive-install.svg?branch=master)](https://travis-ci.org/emgeee/recursive-install) 2 | === 3 | 4 | A small utility to recursively run `npm install` in any child directory that has a `package.json` file excluding sub directories of `node_modules`. 5 | 6 | Install 7 | --- 8 | `$ npm i -g recursive-install` 9 | 10 | Usage 11 | --- 12 | `$ npm-recursive-install` 13 | 14 | `$ npm-recursive-install --skip-root` - Will not install in `process.cwd()` 15 | 16 | `$ npm-recursive-install --rootDir=lib` - Will only install from lib directory 17 | 18 | `$ npm-recursive-install --production` - Will not install dev dependencies 19 | 20 | 21 | License 22 | --- 23 | MIT 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "recursive-install", 3 | "version": "1.4.1", 4 | "description": "CLI tool to recursive search child directories and run 'npm install' when a package.json file is found.", 5 | "main": "recursive-install.js", 6 | "keywords": [ 7 | "install", 8 | "recursive", 9 | "npm install" 10 | ], 11 | "scripts": { 12 | "test": "mocha" 13 | }, 14 | "bin": { 15 | "npm-recursive-install": "./recursive-install.js" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/emgeee/recursive-install" 20 | }, 21 | "author": "Matt Green (https://greenin.space)", 22 | "license": "MIT", 23 | "bugs": { 24 | "url": "https://github.com/emgeee/recursive-install/issues" 25 | }, 26 | "dependencies": { 27 | "shelljs": "^0.8.4", 28 | "yargs": "^16.2.0" 29 | }, 30 | "devDependencies": { 31 | "fs-extra": "^10.0.0", 32 | "mocha": "^9.1.3", 33 | "uuid": "^8.3.2" 34 | }, 35 | "standard": { 36 | "ignore": [ 37 | "public/**", 38 | "src/vendor/**" 39 | ], 40 | "global": [ 41 | "describe", 42 | "before", 43 | "after", 44 | "beforeEach", 45 | "afterEach", 46 | "it" 47 | ] 48 | }, 49 | "engines": { 50 | "node": ">=10" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /recursive-install.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var path = require('path') 4 | var shell = require('shelljs') 5 | var execSync = require('child_process').execSync 6 | var argv = require('yargs').argv 7 | 8 | function noop (x) { return x } 9 | 10 | function getPackageJsonLocations (dirname) { 11 | return shell.find(dirname) 12 | .filter(function (fname) { 13 | return !(fname.indexOf('node_modules') > -1 || fname[0] === '.') && 14 | path.basename(fname) === 'package.json' 15 | }) 16 | .map(function (fname) { 17 | return path.dirname(fname) 18 | }) 19 | } 20 | 21 | function npmInstall (dir) { 22 | var exitCode = 0; 23 | try { 24 | if(argv.production) { 25 | console.log('Installing ' + dir + '/package.json with --production option') 26 | execSync('npm install --production', { cwd: dir}) 27 | } else { 28 | console.log('Installing ' + dir + '/package.json') 29 | execSync('npm install', { cwd: dir}) 30 | } 31 | console.log('') 32 | } catch (err) { 33 | exitCode = err.status 34 | } 35 | 36 | return { 37 | dirname: dir, 38 | exitCode: exitCode 39 | } 40 | } 41 | 42 | function filterRoot (dir) { 43 | if (path.normalize(dir) === path.normalize(process.cwd())) { 44 | console.log('Skipping root package.json') 45 | return false 46 | } else { 47 | return true 48 | } 49 | } 50 | 51 | if (require.main === module) { 52 | var exitCode = getPackageJsonLocations(argv.rootDir ? argv.rootDir : process.cwd()) 53 | .filter(argv.skipRoot ? filterRoot : noop) 54 | .map(npmInstall) 55 | .reduce(function (code, result) { 56 | return result.exitCode > code ? result.exitCode : code 57 | }, 0) 58 | 59 | process.exit(exitCode) 60 | } 61 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var assert = require('assert') 3 | var uuidv4 = require('uuid').v4 4 | var fs = require('fs-extra') 5 | var os = require('os') 6 | var execSync = require('child_process').execSync 7 | 8 | var isWin = process.platform === "win32"; 9 | 10 | var envCommand = (isWin && 'node ') || '' 11 | 12 | var script = envCommand + path.join(__dirname, '../recursive-install.js') 13 | 14 | describe('recursive install', function () { 15 | var cwd 16 | var installedPaths 17 | var notInstalledPaths 18 | var result = { code: 0 } 19 | 20 | beforeEach(function () { 21 | installedPaths = [ 22 | '.', 23 | '/hello/world', 24 | '/foo/bar' 25 | ] 26 | 27 | notInstalledPaths = [ 28 | '/notInstalledPaths/node_modules/a-module' 29 | ] 30 | 31 | cwd = path.join(os.tmpdir(), 'recursive-install'.concat(uuidv4())) 32 | fs.ensureDirSync(cwd) 33 | 34 | installedPaths.concat(notInstalledPaths).forEach(function (p) { 35 | var newPath = path.join(cwd, p) 36 | fs.ensureDirSync(newPath) 37 | fs.copySync(path.join(__dirname, 'test-package.json'), path.join(newPath, 'package.json')) 38 | }) 39 | }) 40 | 41 | afterEach(function () { 42 | if (fs.lstatSync(cwd).isDirectory()) { 43 | fs.removeSync(cwd) 44 | } 45 | }) 46 | 47 | describe('test without option', function () { 48 | beforeEach(function (done) { 49 | this.timeout(60000) // update timeout in case npm install take time 50 | try { 51 | execSync(script, { cwd: cwd }) // Throw an error if exec fail 52 | done() 53 | } catch (err) { 54 | done(err) 55 | } 56 | }) 57 | 58 | it('installs all packages', function () { 59 | installedPaths.forEach(function (p) { 60 | var workingDir = path.join(cwd, p) 61 | assert( 62 | fs.lstatSync(path.join(workingDir, 'node_modules')).isDirectory(), 63 | 'Failed to install for ' + workingDir + '. Directory Listing: ' + fs.readdirSync(workingDir) 64 | ) 65 | 66 | assert( 67 | fs.lstatSync(path.join(workingDir, 'node_modules', 'right-pad')).isDirectory(), 68 | 'Failed to install dev dependencies for ' + workingDir + '. Directory Listing: ' + fs.readdirSync(workingDir) 69 | ) 70 | }) 71 | }) 72 | 73 | it('doesn\'t install packages in node_modules', function () { 74 | notInstalledPaths.forEach(function (p) { 75 | var workingDir = path.join(cwd, p) 76 | assert( 77 | !fs.existsSync(path.join(workingDir, 'node_modules')), 78 | 'Install incorrectly succeeded for ' + workingDir + '. Directory Listing: ' + fs.readdirSync(workingDir) 79 | ) 80 | }) 81 | }) 82 | }) 83 | 84 | describe('test with option --production', function () { 85 | beforeEach(function (done) { 86 | this.timeout(60000) // update timeout in case npm install take time 87 | try { 88 | execSync(script.concat(' --production'), { cwd: cwd }) // Throw an error if exec fail 89 | done() 90 | } catch (err) { 91 | done(err) 92 | } 93 | }) 94 | 95 | it('installs packages, but not devDependencies', function () { 96 | installedPaths.forEach(function (p) { 97 | var workingDir = path.join(cwd, p) 98 | assert( 99 | fs.lstatSync(path.join(workingDir, 'node_modules')).isDirectory(), 100 | 'Failed to install for ' + workingDir + '. Directory Listing: ' + fs.readdirSync(workingDir) 101 | ) 102 | 103 | assert( 104 | !fs.existsSync(path.join(workingDir, 'node_modules', 'right-pad')), 105 | 'Should not install dev dependencies for ' + workingDir + '. Directory Listing: ' + fs.readdirSync(workingDir) 106 | ) 107 | }) 108 | }) 109 | 110 | it('doesn\'t install packages in node_modules', function () { 111 | notInstalledPaths.forEach(function (p) { 112 | var workingDir = path.join(cwd, p) 113 | assert( 114 | !fs.existsSync(path.join(workingDir, 'node_modules')), 115 | 'Install incorrectly succeeded for ' + workingDir + '. Directory Listing: ' + fs.readdirSync(workingDir) 116 | ) 117 | }) 118 | }) 119 | }) 120 | 121 | describe('test with option --skip-root', function () { 122 | beforeEach(function (done) { 123 | this.timeout(60000) // update timeout in case npm install take time 124 | try { 125 | execSync(script.concat(' --skip-root'), { cwd: cwd }) // Throw an error if exec fail 126 | done() 127 | } catch (err) { 128 | done(err) 129 | } 130 | }) 131 | 132 | it('installs packages, but not in root directory', function () { 133 | assert( 134 | !fs.existsSync(path.join(cwd, 'node_modules')), 135 | 'Should not install dependencies for root directory ' + cwd + '. Directory Listing: ' + fs.readdirSync(cwd) 136 | ) 137 | }) 138 | 139 | it('doesn\'t install packages in node_modules', function () { 140 | notInstalledPaths.forEach(function (p) { 141 | var workingDir = path.join(cwd, p) 142 | assert( 143 | !fs.existsSync(path.join(workingDir, 'node_modules')), 144 | 'Install incorrectly succeeded for ' + workingDir + '. Directory Listing: ' + fs.readdirSync(workingDir) 145 | ) 146 | }) 147 | }) 148 | }) 149 | 150 | describe('test with options --skip-root --production', function () { 151 | beforeEach(function (done) { 152 | this.timeout(60000) // update timeout in case npm install take time 153 | try { 154 | execSync(script.concat(' --skip-root --production'), { cwd: cwd }) // Throw an error if exec fail 155 | done() 156 | } catch (err) { 157 | done(err) 158 | } 159 | }) 160 | 161 | it('installs packages, but not in root directory', function () { 162 | assert( 163 | !fs.existsSync(path.join(cwd, 'node_modules')), 164 | 'Should not install dependencies for root directory ' + cwd + '. Directory Listing: ' + fs.readdirSync(cwd) 165 | ) 166 | 167 | installedPaths.shift() // Remove root from installedPaths 168 | installedPaths.forEach(function (p) { 169 | var workingDir = path.join(cwd, p) 170 | assert( 171 | fs.lstatSync(path.join(workingDir, 'node_modules')).isDirectory(), 172 | 'Failed to install for ' + workingDir + '. Directory Listing: ' + fs.readdirSync(workingDir) 173 | ) 174 | 175 | assert( 176 | !fs.existsSync(path.join(workingDir, 'node_modules', 'right-pad')), 177 | 'Should not install dev dependencies for ' + workingDir + '. Directory Listing: ' + fs.readdirSync(workingDir) 178 | ) 179 | }) 180 | }) 181 | 182 | it('doesn\'t install packages in node_modules', function () { 183 | notInstalledPaths.forEach(function (p) { 184 | var workingDir = path.join(cwd, p) 185 | assert( 186 | !fs.existsSync(path.join(workingDir, 'node_modules')), 187 | 'Install incorrectly succeeded for ' + workingDir + '. Directory Listing: ' + fs.readdirSync(workingDir) 188 | ) 189 | }) 190 | }) 191 | }) 192 | 193 | describe('test with fail', function () { 194 | it('fails to install packages', function (done) { 195 | cwd = path.join(os.tmpdir(), 'recursive-install'.concat(uuidv4())) 196 | fs.ensureDirSync(cwd) 197 | fs.copySync(path.join(__dirname, 'test-package-fail.json'), path.join(cwd, 'package.json')) 198 | 199 | try { 200 | execSync(script, { cwd: cwd }) // Throw an error if exec fail 201 | done('Should not succeed') 202 | } catch (err) { 203 | assert( 204 | (err.status === 1), 205 | 'Exist status should be 1, not ' + err.status 206 | ) 207 | done() 208 | } 209 | }) 210 | }) 211 | }) 212 | --------------------------------------------------------------------------------