3 // everything about the installation after the creation of
4 // the .npm/{name}/{version}/package folder.
5 // linking the modules into the npm.root,
6 // resolving dependencies, etc.
8 // This runs AFTER install or link are completed.
10 var npm = require("./npm.js")
11 , log = require("npmlog")
12 , chain = require("slide").chain
13 , fs = require("graceful-fs")
14 , path = require("path")
15 , lifecycle = require("./utils/lifecycle.js")
16 , readJson = require("read-package-json")
17 , link = require("./utils/link.js")
18 , linkIfExists = link.ifExists
19 , cmdShim = require("cmd-shim")
20 , cmdShimIfExists = cmdShim.ifExists
21 , asyncMap = require("slide").asyncMap
23 module.exports = build
24 build.usage = "npm build <folder>\n(this is plumbing)"
28 function build (args, global, didPre, didRB, cb) {
29 if (typeof cb !== "function") cb = didRB, didRB = false
30 if (typeof cb !== "function") cb = didPre, didPre = false
31 if (typeof cb !== "function") {
32 cb = global, global = npm.config.get("global")
34 // it'd be nice to asyncMap these, but actually, doing them
35 // in parallel generally munges up the output from node-waf
36 var builder = build_(global, didPre, didRB)
37 chain(args.map(function (arg) { return function (cb) {
42 function build_ (global, didPre, didRB) { return function (folder, cb) {
43 folder = path.resolve(folder)
44 build._didBuild[folder] = true
45 log.info("build", folder)
46 readJson(path.resolve(folder, "package.json"), function (er, pkg) {
49 ( [ !didPre && [lifecycle, pkg, "preinstall", folder]
50 , [linkStuff, pkg, folder, global, didRB]
51 , pkg.name === "npm" && [writeBuiltinConf, folder]
52 , didPre !== build._noLC && [lifecycle, pkg, "install", folder]
53 , didPre !== build._noLC && [lifecycle, pkg, "postinstall", folder]
54 , didPre !== build._noLC
55 && npm.config.get("npat")
56 && [lifecycle, pkg, "test", folder] ]
61 function writeBuiltinConf (folder, cb) {
62 // the builtin config is "sticky". Any time npm installs itself,
63 // it puts its builtin config file there, as well.
64 if (!npm.config.usingBuiltin
65 || folder !== path.dirname(__dirname)) {
68 npm.config.save("builtin", cb)
71 function linkStuff (pkg, folder, global, didRB, cb) {
72 // allow to opt out of linking binaries.
73 if (npm.config.get("bin-links") === false) return cb()
75 // if it's global, and folder is in {prefix}/node_modules,
76 // then bins are in {prefix}/bin
77 // otherwise, then bins are in folder/../.bin
78 var parent = path.dirname(folder)
79 , gnm = global && npm.globalDir
80 , top = parent === npm.dir
81 , gtop = parent === gnm
83 log.verbose("linkStuff", [global, gnm, gtop, parent])
84 log.info("linkStuff", pkg._id)
86 shouldWarn(pkg, folder, global, function() {
87 asyncMap( [linkBins, linkMans, !didRB && rebuildBundles]
90 log.verbose(fn.name, pkg._id)
91 fn(pkg, folder, parent, gtop, cb)
96 function shouldWarn(pkg, folder, global, cb) {
97 var parent = path.dirname(folder)
98 , top = parent === npm.dir
101 readJson(path.resolve(cwd, "package.json"), function(er, topPkg) {
102 if (er) return cb(er)
104 var linkedPkg = path.basename(cwd)
105 , currentPkg = path.basename(folder)
107 // current searched package is the linked package on first call
108 if (linkedPkg !== currentPkg) {
110 if (!topPkg.dependencies) return cb()
112 // don't generate a warning if it's listed in dependencies
113 if (Object.keys(topPkg.dependencies).indexOf(currentPkg) === -1) {
115 if (top && pkg.preferGlobal && !global) {
116 log.warn("prefer global", pkg._id + " should be installed with -g")
125 function rebuildBundles (pkg, folder, parent, gtop, cb) {
126 if (!npm.config.get("rebuild-bundle")) return cb()
128 var deps = Object.keys(pkg.dependencies || {})
129 .concat(Object.keys(pkg.devDependencies || {}))
130 , bundles = pkg.bundleDependencies || pkg.bundledDependencies || []
132 fs.readdir(path.resolve(folder, "node_modules"), function (er, files) {
133 // error means no bundles
136 log.verbose("rebuildBundles", files)
137 // don't asyncMap these, because otherwise build script output
138 // gets interleaved and is impossible to read
139 chain(files.filter(function (file) {
141 // not a .folder, like .bin or .hooks
142 return !file.match(/^[\._-]/)
143 // not some old 0.x style bundle
144 && file.indexOf("@") === -1
145 // either not a dep, or explicitly bundled
146 && (deps.indexOf(file) === -1 || bundles.indexOf(file) !== -1)
147 }).map(function (file) {
148 file = path.resolve(folder, "node_modules", file)
149 return function (cb) {
150 if (build._didBuild[file]) return cb()
151 log.verbose("rebuild bundle", file)
152 // if file is not a package dir, then don't do it.
153 fs.lstat(path.resolve(file, "package.json"), function (er, st) {
155 build_(false)(file, cb)
161 function linkBins (pkg, folder, parent, gtop, cb) {
162 if (!pkg.bin || !gtop && path.basename(parent) !== "node_modules") {
165 var binRoot = gtop ? npm.globalBin
166 : path.resolve(parent, ".bin")
167 log.verbose("link bins", [pkg.bin, binRoot, gtop])
169 asyncMap(Object.keys(pkg.bin), function (b, cb) {
170 linkBin( path.resolve(folder, pkg.bin[b])
171 , path.resolve(binRoot, b)
174 if (er) return cb(er)
175 // bins should always be executable.
176 // XXX skip chmod on windows?
177 var src = path.resolve(folder, pkg.bin[b])
178 fs.chmod(src, npm.modes.exec, function (er) {
179 if (er && er.code === "ENOENT" && npm.config.get("ignore-scripts")) {
182 if (er || !gtop) return cb(er)
183 var dest = path.resolve(binRoot, b)
184 , out = npm.config.get("parseable")
185 ? dest + "::" + src + ":BINFILE"
186 : dest + " -> " + src
194 function linkBin (from, to, gently, cb) {
195 if (process.platform !== "win32") {
196 return linkIfExists(from, to, gently, cb)
198 return cmdShimIfExists(from, to, cb)
202 function linkMans (pkg, folder, parent, gtop, cb) {
203 if (!pkg.man || !gtop || process.platform === "win32") return cb()
205 var manRoot = path.resolve(npm.config.get("prefix"), "share", "man")
207 // make sure that the mans are unique.
208 // otherwise, if there are dupes, it'll fail with EEXIST
209 var set = pkg.man.reduce(function (acc, man) {
210 acc[path.basename(man)] = man
213 pkg.man = pkg.man.filter(function (man) {
214 return set[path.basename(man)] === man
217 asyncMap(pkg.man, function (man, cb) {
218 if (typeof man !== "string") return cb()
219 var parseMan = man.match(/(.*\.([0-9]+)(\.gz)?)$/)
222 , gz = parseMan[3] || ""
223 , bn = path.basename(stem)
224 , manDest = path.join(manRoot, "man" + sxn, bn)
226 linkIfExists(man, manDest, gtop && folder, cb)