1 exports = module.exports = lifecycle
3 exports.makeEnv = makeEnv
5 var log = require("npmlog")
6 var spawn = require("./spawn")
7 var npm = require("../npm.js")
8 var path = require("path")
9 var fs = require("graceful-fs")
10 var chain = require("slide").chain
11 var Stream = require("stream").Stream
13 var uidNumber = require("uid-number")
14 var umask = require("./umask")
16 // windows calls it's path "Path" usually, but this is not guaranteed.
17 if (process.platform === "win32") {
19 Object.keys(process.env).forEach(function (e) {
20 if (e.match(/^PATH$/i)) {
26 function lifecycle (pkg, stage, wd, unsafe, failOk, cb) {
27 if (typeof cb !== "function") cb = failOk, failOk = false
28 if (typeof cb !== "function") cb = unsafe, unsafe = false
29 if (typeof cb !== "function") cb = wd, wd = null
31 while (pkg && pkg._data) pkg = pkg._data
32 if (!pkg) return cb(new Error("Invalid package data"))
34 log.info(stage, pkg._id)
35 if (!pkg.scripts || npm.config.get('ignore-scripts')) pkg.scripts = {}
37 validWd(wd || path.resolve(npm.dir, pkg.name), function (er, wd) {
40 unsafe = unsafe || npm.config.get("unsafe-perm")
42 if ((wd.indexOf(npm.dir) !== 0 ||
43 wd.indexOf(pkg.name) !== wd.length - pkg.name.length) &&
44 !unsafe && pkg.scripts[stage]) {
45 log.warn( "cannot run in wd", "%s %s (wd=%s)"
46 , pkg._id, pkg.scripts[stage], wd)
50 // set the env variables, then run scripts as a child process.
51 var env = makeEnv(pkg)
52 env.npm_lifecycle_event = stage
53 env.npm_node_execpath = env.NODE = env.NODE || process.execPath
54 env.npm_execpath = require.main.filename
56 // "nobody" typically doesn't have permission to write to /tmp
57 // even if it's never used, sh freaks out.
58 if (!npm.config.get("unsafe-perm")) env.TMPDIR = wd
60 lifecycle_(pkg, stage, wd, env, unsafe, failOk, cb)
64 function checkForLink (pkg, cb) {
65 var f = path.join(npm.dir, pkg.name)
66 fs.lstat(f, function (er, s) {
67 cb(null, !(er || !s.isSymbolicLink()))
71 function lifecycle_ (pkg, stage, wd, env, unsafe, failOk, cb) {
73 , p = wd.split("node_modules")
74 , acc = path.resolve(p.shift())
76 p.forEach(function (pp) {
77 pathArr.unshift(path.join(acc, "node_modules", ".bin"))
78 acc = path.join(acc, "node_modules", pp)
80 pathArr.unshift(path.join(acc, "node_modules", ".bin"))
82 // we also unshift the bundled node-gyp-bin folder so that
83 // the bundled one will be used for installing things.
84 pathArr.unshift(path.join(__dirname, "..", "..", "bin", "node-gyp-bin"))
86 // prefer current node interpreter in child scripts
87 pathArr.push(path.dirname(process.execPath))
89 if (env[PATH]) pathArr.push(env[PATH])
90 env[PATH] = pathArr.join(process.platform === "win32" ? ";" : ":")
92 var packageLifecycle = pkg.scripts && pkg.scripts.hasOwnProperty(stage)
94 if (packageLifecycle) {
95 // define this here so it's available to all scripts.
96 env.npm_lifecycle_script = pkg.scripts[stage]
101 if (npm.config.get("force")) {
102 log.info("forced, continuing", er)
105 log.warn("continuing anyway", er.message)
113 ( [ packageLifecycle && [runPackageLifecycle, pkg, env, wd, unsafe]
114 , [runHookLifecycle, pkg, env, wd, unsafe] ]
118 function validWd (d, cb) {
119 fs.stat(d, function (er, st) {
120 if (er || !st.isDirectory()) {
121 var p = path.dirname(d)
123 return cb(new Error("Could not find suitable wd"))
125 return validWd(p, cb)
131 function runPackageLifecycle (pkg, env, wd, unsafe, cb) {
132 // run package lifecycle scripts in the package root, or the nearest parent.
133 var stage = env.npm_lifecycle_event
134 , cmd = env.npm_lifecycle_script
136 var note = "\n> " + pkg._id + " " + stage + " " + wd
137 + "\n> " + cmd + "\n"
138 runCmd(note, cmd, pkg, env, stage, wd, unsafe, cb)
147 var r = queue.shift()
148 runCmd.apply(null, r)
152 function runCmd (note, cmd, pkg, env, stage, wd, unsafe, cb) {
154 queue.push([note, cmd, pkg, env, stage, wd, unsafe, cb])
160 var user = unsafe ? null : npm.config.get("user")
161 , group = unsafe ? null : npm.config.get("group")
163 if (log.level !== 'silent') {
164 if (npm.spinner.int) {
165 npm.config.get("logstream").write("\r \r")
169 log.verbose("unsafe-perm in lifecycle", unsafe)
171 if (process.platform === "win32") {
176 runCmd_(cmd, pkg, env, wd, stage, unsafe, 0, 0, cb)
178 uidNumber(user, group, function (er, uid, gid) {
179 runCmd_(cmd, pkg, env, wd, stage, unsafe, uid, gid, cb)
184 function runCmd_ (cmd, pkg, env, wd, stage, unsafe, uid, gid, cb_) {
187 cb_.apply(null, arguments)
189 process.nextTick(dequeue)
205 if (process.platform === 'win32') {
206 sh = process.env.comspec || 'cmd'
208 conf.windowsVerbatimArguments = true
211 var proc = spawn(sh, [shFlag, cmd], conf)
212 proc.on("error", procError)
213 proc.on("close", function (code, signal) {
215 process.kill(process.pid, signal);
217 var er = new Error("Exit status " + code)
222 function procError (er) {
223 if (er && !npm.ROLLBACK) {
224 log.info(pkg._id, "Failed to exec "+stage+" script")
225 er.message = pkg._id + " "
226 + stage + ": `" + cmd +"`\n"
228 if (er.code !== "EPERM") {
229 er.code = "ELIFECYCLE"
234 er.pkgname = pkg.name
237 log.error(pkg._id+"."+stage, er)
238 log.error(pkg._id+"."+stage, "continuing anyway")
246 function runHookLifecycle (pkg, env, wd, unsafe, cb) {
247 // check for a hook script, run if present.
248 var stage = env.npm_lifecycle_event
249 , hook = path.join(npm.dir, ".hooks", stage)
250 , user = unsafe ? null : npm.config.get("user")
251 , group = unsafe ? null : npm.config.get("group")
254 fs.stat(hook, function (er) {
256 var note = "\n> " + pkg._id + " " + stage + " " + wd
258 runCmd(note, hook, pkg, env, stage, wd, unsafe, cb)
262 function makeEnv (data, prefix, env) {
263 prefix = prefix || "npm_package_"
266 for (var i in process.env) if (!i.match(/^npm_/)) {
267 env[i] = process.env[i]
270 // npat asks for tap output
271 if (npm.config.get("npat")) env.TAP = 1
273 // express and others respect the NODE_ENV value.
274 if (npm.config.get("production")) env.NODE_ENV = "production"
276 } else if (!data.hasOwnProperty("_lifecycleEnv")) {
277 Object.defineProperty(data, "_lifecycleEnv",
283 for (var i in data) if (i.charAt(0) !== "_") {
284 var envKey = (prefix+i).replace(/[^a-zA-Z0-9_]/g, '_')
285 if (i === "readme") {
288 if (data[i] && typeof(data[i]) === "object") {
290 // quick and dirty detection for cyclical structures
291 JSON.stringify(data[i])
292 makeEnv(data[i], envKey+"_", env)
294 // usually these are package objects.
295 // just get the path and basic details.
297 makeEnv( { name: d.name, version: d.version, path:d.path }
301 env[envKey] = String(data[i])
302 env[envKey] = -1 !== env[envKey].indexOf("\n")
303 ? JSON.stringify(env[envKey])
309 if (prefix !== "npm_package_") return env
311 prefix = "npm_config_"
313 , keys = npm.config.keys
315 , namePref = data.name + ":"
316 , verPref = data.name + "@" + data.version + ":"
318 keys.forEach(function (i) {
319 // in some rare cases (e.g. working with nerf darts), there are segmented
320 // "private" (underscore-prefixed) config names -- don't export
321 if (i.charAt(0) === '_' && i.indexOf('_' + namePref) !== 0 || i.match(/:_/)) {
324 var value = npm.config.get(i)
325 if (value instanceof Stream || Array.isArray(value)) return
326 if (i.match(/umask/)) value = umask.toString(value)
327 if (!value) value = ""
328 else if (typeof value === "number") value = "" + value
329 else if (typeof value !== "string") value = JSON.stringify(value)
331 value = -1 !== value.indexOf("\n")
332 ? JSON.stringify(value)
334 i = i.replace(/^_+/, "")
335 if (i.indexOf(namePref) === 0) {
336 var k = i.substr(namePref.length).replace(/[^a-zA-Z0-9_]/g, "_")
337 pkgConfig[ k ] = value
338 } else if (i.indexOf(verPref) === 0) {
339 var k = i.substr(verPref.length).replace(/[^a-zA-Z0-9_]/g, "_")
340 pkgVerConfig[ k ] = value
342 var envKey = (prefix+i).replace(/[^a-zA-Z0-9_]/g, "_")
346 prefix = "npm_package_config_"
347 ;[pkgConfig, pkgVerConfig].forEach(function (conf) {
348 for (var i in conf) {
349 var envKey = (prefix+i)
350 env[envKey] = conf[i]
357 function cmd (stage) {
358 function CMD (args, cb) {
359 npm.commands["run-script"]([stage].concat(args), cb)
361 CMD.usage = "npm "+stage+" [-- <args>]"
362 var installedShallow = require("./completion/installed-shallow.js")
363 CMD.completion = function (opts, cb) {
364 installedShallow(opts, function (d) {
365 return d.scripts && d.scripts[stage]