Skip to content

Commit bf1c50f

Browse files
authored
Install only the dependencies specified in buildDependencies, if it exists (#20)
* Install only the dependencies specified in buildDependencies, if it exists * Don't use arrow function syntax * Don't use npm-package-arg * Add newline to message [ci skip] * Break steps into functions
1 parent 1c1c602 commit bf1c50f

File tree

5 files changed

+170
-38
lines changed

5 files changed

+170
-38
lines changed

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ npm install postinstall-build --save
2121
- [Why?](#why)
2222
- [Usage](#usage)
2323
- [Options](#options)
24+
- [Specifying build dependencies in package.json](#specifying-build-dependencies-in-packagejson)
2425
- [Examples](#examples)
2526
- [Motivation](#motivation)
2627
- [Caveats](#caveats)
@@ -81,6 +82,15 @@ portability – Windows does not treat single-quoted strings as a single
8182
parameter. (This is the case in any npm script regardless of `postinstall-build`
8283
usage.)
8384

85+
#### Specifying build dependencies in package.json
86+
87+
If you specify a `buildDependencies` array in `package.json`, you can control
88+
which dependencies are installed before your build command is run. `buildDependencies`
89+
must be an array of package names that also appear in `devDependencies`. If a
90+
package named in `buildDependencies` does not exist in `devDependencies`, then
91+
it is assumed to already be available (as a global, peer, or production
92+
dependency), will not be installed, and a warning will be printed.
93+
8494
## Examples
8595

8696
Run the `build` script (the default) if `lib` doesn’t exist during `postinstall`:
@@ -123,6 +133,31 @@ Run a non-npm script:
123133
}
124134
```
125135

136+
Install only the necessary build dependencies:
137+
138+
```json
139+
{
140+
"scripts": {
141+
"build": "babel --presets es2015 --out-dir lib src",
142+
"postinstall": "postinstall-build lib"
143+
},
144+
"dependencies": {
145+
"postinstall-build": "^3.0.0"
146+
},
147+
"devDependencies": {
148+
"ava": "latest",
149+
"babel-cli": "^6.0.0",
150+
"babel-preset-es2015": "^6.0.0",
151+
"nyc": "latest",
152+
"prettier": "latest"
153+
},
154+
"buildDependencies": [
155+
"babel-cli",
156+
"babel-preset-es2015"
157+
]
158+
}
159+
```
160+
126161
---
127162

128163
⚠️ **INCORRECT USAGE** ⚠️

index.js

Lines changed: 118 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -159,60 +159,144 @@ function postinstallBuild () {
159159
return
160160
}
161161

162-
// If we're the top-level package being `npm install`ed with no
163-
// arguments, we still might want to prune if certain flags indicate that
164-
// only production dependencies were requested.
162+
// If we're the top-level package being `npm install`ed with no arguments,
163+
// we still might want to prune if certain flags indicate that only production
164+
// dependencies were requested.
165165
var isProduction = (process.env.npm_config_production === 'true' &&
166166
process.env.npm_config_only !== 'development' &&
167167
process.env.npm_config_only !== 'dev')
168168
var isOnlyProduction = (process.env.npm_config_only === 'production' ||
169169
process.env.npm_config_only === 'prod')
170-
var prune = isDependency || isProduction || isOnlyProduction
171-
172-
fs.stat(buildArtifact, function (err, stats) {
173-
if (err || !(stats.isFile() || stats.isDirectory())) {
174-
// This script will run again after we run `npm install` below. Set an
175-
// environment variable to tell it to skip the check. Really we just want
176-
// the spawned child's `env` to be modified, but it's easier just modify
177-
// and pass along our entire `process.env`.
170+
var shouldPrune = isDependency || isProduction || isOnlyProduction
171+
172+
var handleError = function (err) {
173+
console.error(err)
174+
safeExit(1)
175+
}
176+
177+
var getInstallArgs = function () {
178+
var packageFile = path.join(CWD, 'package.json')
179+
var packageInfo = require(packageFile)
180+
var devDependencies = packageInfo.devDependencies || {}
181+
var buildDependencies = packageInfo.buildDependencies
182+
var installArgs = ' --only=dev'
183+
184+
if (buildDependencies && Array.isArray(buildDependencies)) {
185+
installArgs = buildDependencies.map(function (name) {
186+
var spec = devDependencies[name]
187+
// If a name specified in `buildDependencies` doesn't actually exist in
188+
// `devDependencies`, it may be a global, peer, or production dependency.
189+
// Assume it is already available. If a user puts something in
190+
// `buildDependencies` and expects it to be installed by
191+
// `postinstall-build`, then it must also be in `devDependencies`.
192+
if (typeof spec === 'undefined') {
193+
if (!flags.silent) {
194+
console.warn(
195+
"postinstall-build:\n The dependency '" + name + "' appears in " +
196+
'buildDependencies but not devDependencies.\n Instead of ' +
197+
'installing it, postinstall-build will assume it is already ' +
198+
'available.\n'
199+
)
200+
}
201+
return ''
202+
} else if (spec) {
203+
// This previously used `npm-package-arg` to determine which specs are
204+
// appropriate to use with the @ syntax and which aren't, but the latest
205+
// version no longer works on Node <4, and older versions don't have the
206+
// properties we want. So instead of trying to parse `spec`, just assume
207+
// npm is okay with always using @ syntax.
208+
return ' "' + name + '@' + spec + '"'
209+
} else {
210+
// We shouldn't really expect to find a null or empty spec, but it's
211+
// technically possible, and npm will interpret it as `latest`.
212+
return ' "' + name + '"'
213+
}
214+
}).join('')
215+
}
216+
217+
return installArgs
218+
}
219+
220+
var checkBuildArtifact = function (callback) {
221+
fs.stat(buildArtifact, function (err, stats) {
222+
if (err || !(stats.isFile() || stats.isDirectory())) {
223+
callback(null, true)
224+
} else {
225+
callback(null, false)
226+
}
227+
})
228+
}
229+
230+
var installBuildDependencies = function (execOpts, callback) {
231+
// We only need to install dependencies if `shouldPrune` is true. Why?
232+
// Because this flag detects whether `devDependencies` were already
233+
// installed in order to determine whether they need to be pruned at the
234+
// end or not. And if we already have `devDependencies` then installing
235+
// here isn't going to get us anything.
236+
if (shouldPrune) {
237+
// If `installArgs` is empty, the build doesn't depend on installing any
238+
// extra dependencies.
239+
var installArgs = getInstallArgs()
240+
if (installArgs) {
241+
return exec(npm + ' install' + installArgs, execOpts, callback)
242+
}
243+
}
244+
callback(null)
245+
}
246+
247+
var runBuildCommand = function (execOpts, callback) {
248+
// Only quote the build command if necessary, otherwise run it exactly
249+
// as npm would.
250+
execOpts.quote = flags.quote
251+
exec(buildCommand, execOpts, callback)
252+
}
253+
254+
var cleanUp = function (execOpts, callback) {
255+
if (shouldPrune) {
256+
execOpts.quote = true
257+
return exec(npm + ' prune --production', execOpts, callback)
258+
}
259+
callback(null)
260+
}
261+
262+
checkBuildArtifact(function (err, shouldBuild) {
263+
if (err) {
264+
return handleError(err)
265+
}
266+
if (shouldBuild) {
267+
// If `npm install` ends up being run by `installBuildDependencies`, this
268+
// script will be run again. Set an environment variable to tell it to
269+
// skip the check. Really we just want the spawned child's `env` to be
270+
// modified, but it's easier just modify and pass along our entire
271+
// `process.env`.
178272
process.env.POSTINSTALL_BUILD_CWD = CWD
179273

180-
var opts = {
274+
var execOpts = {
181275
env: process.env,
182276
quote: true
183277
}
184278
if (flags.silent) {
185-
opts.stdio = 'ignore'
279+
execOpts.stdio = 'ignore'
186280
}
187281

188-
// We already have prod dependencies, that's what triggered `postinstall`
189-
// in the first place. So only install dev.
190-
exec(npm + ' install --only=dev', opts, function (err) {
282+
installBuildDependencies(execOpts, function (err) {
191283
if (err) {
192-
console.error(err)
193-
return safeExit(1)
284+
return handleError(err)
194285
}
195-
// Don't need the flag anymore as `postinstall` was already run.
286+
// Don't need this flag anymore as `postinstall` was already run.
196287
// Change it back so the environment is minimally changed for the
197288
// remaining commands.
198289
process.env.POSTINSTALL_BUILD_CWD = POSTINSTALL_BUILD_CWD
199-
// Only quote the build command if necessary, otherwise run it exactly
200-
// as npm would.
201-
opts.quote = flags.quote
202-
exec(buildCommand, opts, function (err) {
290+
291+
runBuildCommand(execOpts, function (err) {
203292
if (err) {
204-
console.error(err)
205-
return safeExit(1)
206-
}
207-
if (prune) {
208-
opts.quote = true
209-
exec(npm + ' prune --production', opts, function (err) {
210-
if (err) {
211-
console.error(err)
212-
return safeExit(1)
213-
}
214-
})
293+
return handleError(err)
215294
}
295+
cleanUp(execOpts, function (err) {
296+
if (err) {
297+
return handleError(err)
298+
}
299+
})
216300
})
217301
})
218302
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,6 @@
4545
"rimraf": "^2.5.4",
4646
"snazzy": "^6.0.0",
4747
"standard": "^10.0.0"
48-
}
48+
},
49+
"dependencies": {}
4950
}

test/package-c/ensure-no-mocha.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
try {
2+
require('mocha')
3+
process.exit(1)
4+
} catch (err) {
5+
process.exit(0)
6+
}

test/package-c/package.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"description": "Test that we can both depend on multiple packages that use postinstall-build, and also depend on a package that depends on a package that uses postinstall-build",
66
"main": "lib/index.js",
77
"scripts": {
8-
"build": "babel --presets es2015 -d lib src",
8+
"build": "node ./ensure-no-mocha.js && babel --presets es2015 -d lib src",
99
"postinstall": "postinstall-build lib"
1010
},
1111
"author": "",
@@ -17,6 +17,12 @@
1717
},
1818
"devDependencies": {
1919
"babel-cli": "^6.0.0",
20-
"babel-preset-es2015": "^6.0.0"
21-
}
20+
"babel-preset-es2015": "^6.0.0",
21+
"mocha": "latest"
22+
},
23+
"buildDependencies": [
24+
"babel-cli",
25+
"babel-preset-es2015",
26+
"jasmine"
27+
]
2228
}

0 commit comments

Comments
 (0)