@@ -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 }
0 commit comments