| // npm build command |
| |
| // everything about the installation after the creation of |
| // the .npm/{name}/{version}/package folder. |
| // linking the modules into the npm.root, |
| // resolving dependencies, etc. |
| |
| // This runs AFTER install or link are completed. |
| |
| var npm = require("./npm.js") |
| , log = require("npmlog") |
| , chain = require("slide").chain |
| , fs = require("graceful-fs") |
| , path = require("path") |
| , lifecycle = require("./utils/lifecycle.js") |
| , readJson = require("read-package-json") |
| , link = require("./utils/link.js") |
| , linkIfExists = link.ifExists |
| , cmdShim = require("cmd-shim") |
| , cmdShimIfExists = cmdShim.ifExists |
| , asyncMap = require("slide").asyncMap |
| |
| module.exports = build |
| build.usage = "npm build <folder>\n(this is plumbing)" |
| |
| build._didBuild = {} |
| build._noLC = {} |
| function build (args, global, didPre, didRB, cb) { |
| if (typeof cb !== "function") cb = didRB, didRB = false |
| if (typeof cb !== "function") cb = didPre, didPre = false |
| if (typeof cb !== "function") { |
| cb = global, global = npm.config.get("global") |
| } |
| // it'd be nice to asyncMap these, but actually, doing them |
| // in parallel generally munges up the output from node-waf |
| var builder = build_(global, didPre, didRB) |
| chain(args.map(function (arg) { return function (cb) { |
| builder(arg, cb) |
| }}), cb) |
| } |
| |
| function build_ (global, didPre, didRB) { return function (folder, cb) { |
| folder = path.resolve(folder) |
| build._didBuild[folder] = true |
| log.info("build", folder) |
| readJson(path.resolve(folder, "package.json"), function (er, pkg) { |
| if (er) return cb(er) |
| chain |
| ( [ !didPre && [lifecycle, pkg, "preinstall", folder] |
| , [linkStuff, pkg, folder, global, didRB] |
| , pkg.name === "npm" && [writeBuiltinConf, folder] |
| , didPre !== build._noLC && [lifecycle, pkg, "install", folder] |
| , didPre !== build._noLC && [lifecycle, pkg, "postinstall", folder] |
| , didPre !== build._noLC |
| && npm.config.get("npat") |
| && [lifecycle, pkg, "test", folder] ] |
| , cb ) |
| }) |
| }} |
| |
| function writeBuiltinConf (folder, cb) { |
| // the builtin config is "sticky". Any time npm installs itself, |
| // it puts its builtin config file there, as well. |
| if (!npm.config.usingBuiltin |
| || folder !== path.dirname(__dirname)) { |
| return cb() |
| } |
| npm.config.save("builtin", cb) |
| } |
| |
| function linkStuff (pkg, folder, global, didRB, cb) { |
| // allow to opt out of linking binaries. |
| if (npm.config.get("bin-links") === false) return cb() |
| |
| // if it's global, and folder is in {prefix}/node_modules, |
| // then bins are in {prefix}/bin |
| // otherwise, then bins are in folder/../.bin |
| var parent = path.dirname(folder) |
| , gnm = global && npm.globalDir |
| , top = parent === npm.dir |
| , gtop = parent === gnm |
| |
| log.verbose("linkStuff", [global, gnm, gtop, parent]) |
| log.info("linkStuff", pkg._id) |
| |
| shouldWarn(pkg, folder, global, function() { |
| asyncMap( [linkBins, linkMans, !didRB && rebuildBundles] |
| , function (fn, cb) { |
| if (!fn) return cb() |
| log.verbose(fn.name, pkg._id) |
| fn(pkg, folder, parent, gtop, cb) |
| }, cb) |
| }) |
| } |
| |
| function shouldWarn(pkg, folder, global, cb) { |
| var parent = path.dirname(folder) |
| , top = parent === npm.dir |
| , cwd = process.cwd() |
| |
| readJson(path.resolve(cwd, "package.json"), function(er, topPkg) { |
| if (er) return cb(er) |
| |
| var linkedPkg = path.basename(cwd) |
| , currentPkg = path.basename(folder) |
| |
| // current searched package is the linked package on first call |
| if (linkedPkg !== currentPkg) { |
| |
| if (!topPkg.dependencies) return cb() |
| |
| // don't generate a warning if it's listed in dependencies |
| if (Object.keys(topPkg.dependencies).indexOf(currentPkg) === -1) { |
| |
| if (top && pkg.preferGlobal && !global) { |
| log.warn("prefer global", pkg._id + " should be installed with -g") |
| } |
| } |
| } |
| |
| cb() |
| }) |
| } |
| |
| function rebuildBundles (pkg, folder, parent, gtop, cb) { |
| if (!npm.config.get("rebuild-bundle")) return cb() |
| |
| var deps = Object.keys(pkg.dependencies || {}) |
| .concat(Object.keys(pkg.devDependencies || {})) |
| , bundles = pkg.bundleDependencies || pkg.bundledDependencies || [] |
| |
| fs.readdir(path.resolve(folder, "node_modules"), function (er, files) { |
| // error means no bundles |
| if (er) return cb() |
| |
| log.verbose("rebuildBundles", files) |
| // don't asyncMap these, because otherwise build script output |
| // gets interleaved and is impossible to read |
| chain(files.filter(function (file) { |
| // rebuild if: |
| // not a .folder, like .bin or .hooks |
| return !file.match(/^[\._-]/) |
| // not some old 0.x style bundle |
| && file.indexOf("@") === -1 |
| // either not a dep, or explicitly bundled |
| && (deps.indexOf(file) === -1 || bundles.indexOf(file) !== -1) |
| }).map(function (file) { |
| file = path.resolve(folder, "node_modules", file) |
| return function (cb) { |
| if (build._didBuild[file]) return cb() |
| log.verbose("rebuild bundle", file) |
| // if file is not a package dir, then don't do it. |
| fs.lstat(path.resolve(file, "package.json"), function (er, st) { |
| if (er) return cb() |
| build_(false)(file, cb) |
| }) |
| }}), cb) |
| }) |
| } |
| |
| function linkBins (pkg, folder, parent, gtop, cb) { |
| if (!pkg.bin || !gtop && path.basename(parent) !== "node_modules") { |
| return cb() |
| } |
| var binRoot = gtop ? npm.globalBin |
| : path.resolve(parent, ".bin") |
| log.verbose("link bins", [pkg.bin, binRoot, gtop]) |
| |
| asyncMap(Object.keys(pkg.bin), function (b, cb) { |
| linkBin( path.resolve(folder, pkg.bin[b]) |
| , path.resolve(binRoot, b) |
| , gtop && folder |
| , function (er) { |
| if (er) return cb(er) |
| // bins should always be executable. |
| // XXX skip chmod on windows? |
| var src = path.resolve(folder, pkg.bin[b]) |
| fs.chmod(src, npm.modes.exec, function (er) { |
| if (er && er.code === "ENOENT" && npm.config.get("ignore-scripts")) { |
| return cb() |
| } |
| if (er || !gtop) return cb(er) |
| var dest = path.resolve(binRoot, b) |
| , out = npm.config.get("parseable") |
| ? dest + "::" + src + ":BINFILE" |
| : dest + " -> " + src |
| console.log(out) |
| cb() |
| }) |
| }) |
| }, cb) |
| } |
| |
| function linkBin (from, to, gently, cb) { |
| if (process.platform !== "win32") { |
| return linkIfExists(from, to, gently, cb) |
| } else { |
| return cmdShimIfExists(from, to, cb) |
| } |
| } |
| |
| function linkMans (pkg, folder, parent, gtop, cb) { |
| if (!pkg.man || !gtop || process.platform === "win32") return cb() |
| |
| var manRoot = path.resolve(npm.config.get("prefix"), "share", "man") |
| |
| // make sure that the mans are unique. |
| // otherwise, if there are dupes, it'll fail with EEXIST |
| var set = pkg.man.reduce(function (acc, man) { |
| acc[path.basename(man)] = man |
| return acc |
| }, {}) |
| pkg.man = pkg.man.filter(function (man) { |
| return set[path.basename(man)] === man |
| }) |
| |
| asyncMap(pkg.man, function (man, cb) { |
| if (typeof man !== "string") return cb() |
| var parseMan = man.match(/(.*\.([0-9]+)(\.gz)?)$/) |
| , stem = parseMan[1] |
| , sxn = parseMan[2] |
| , gz = parseMan[3] || "" |
| , bn = path.basename(stem) |
| , manDest = path.join(manRoot, "man" + sxn, bn) |
| |
| linkIfExists(man, manDest, gtop && folder, cb) |
| }, cb) |
| } |