module.exports = publish var url = require("url") , npm = require("./npm.js") , log = require("npmlog") , path = require("path") , readJson = require("read-package-json") , lifecycle = require("./utils/lifecycle.js") , chain = require("slide").chain , Conf = require("npmconf").Conf , RegClient = require("npm-registry-client") , mapToRegistry = require("./utils/map-to-registry.js") publish.usage = "npm publish " + "\nnpm publish " + "\n\nPublishes '.' if no argument supplied" publish.completion = function (opts, cb) { // publish can complete to a folder with a package.json // or a tarball, or a tarball url. // for now, not yet implemented. return cb() } function publish (args, isRetry, cb) { if (typeof cb !== "function") { cb = isRetry isRetry = false } if (args.length === 0) args = ["."] if (args.length !== 1) return cb(publish.usage) log.verbose("publish", args) var arg = args[0] // if it's a local folder, then run the prepublish there, first. readJson(path.resolve(arg, "package.json"), function (er, data) { er = needVersion(er, data) if (er && er.code !== "ENOENT" && er.code !== "ENOTDIR") return cb(er) // error is ok. could be publishing a url or tarball // however, that means that we will not have automatically run // the prepublish script, since that gets run when adding a folder // to the cache. if (er) return cacheAddPublish(arg, false, isRetry, cb) cacheAddPublish(arg, true, isRetry, cb) }) } // didPre in this case means that we already ran the prepublish script, // and that the "dir" is an actual directory, and not something silly // like a tarball or name@version thing. // That means that we can run publish/postpublish in the dir, rather than // in the cache dir. function cacheAddPublish (dir, didPre, isRetry, cb) { npm.commands.cache.add(dir, null, null, false, function (er, data) { if (er) return cb(er) log.silly("publish", data) var cachedir = path.resolve( npm.cache , data.name , data.version , "package" ) chain([ !didPre && [lifecycle, data, "prepublish", cachedir] , [publish_, dir, data, isRetry, cachedir] , [lifecycle, data, "publish", didPre ? dir : cachedir] , [lifecycle, data, "postpublish", didPre ? dir : cachedir] ] , cb ) }) } function publish_ (arg, data, isRetry, cachedir, cb) { if (!data) return cb(new Error("no package.json file found")) var registry = npm.registry var config = npm.config // check for publishConfig hash if (data.publishConfig) { config = new Conf(npm.config) config.save = npm.config.save.bind(npm.config) // don't modify the actual publishConfig object, in case we have // to set a login token or some other data. config.unshift(Object.keys(data.publishConfig).reduce(function (s, k) { s[k] = data.publishConfig[k] return s }, {})) registry = new RegClient(config) } data._npmVersion = npm.version delete data.modules if (data.private) return cb( new Error( "This package has been marked as private\n" + "Remove the 'private' field from the package.json to publish it." ) ) mapToRegistry(data.name, config, function (er, registryURI) { if (er) return cb(er) var tarball = cachedir + ".tgz" // we just want the base registry URL in this case var registryBase = url.resolve(registryURI, ".") log.verbose("publish", "registryBase", registryBase) var c = config.getCredentialsByURI(registryBase) data._npmUser = {name: c.username, email: c.email} registry.publish(registryBase, data, tarball, function (er) { if (er && er.code === "EPUBLISHCONFLICT" && npm.config.get("force") && !isRetry) { log.warn("publish", "Forced publish over " + data._id) return npm.commands.unpublish([data._id], function (er) { // ignore errors. Use the force. Reach out with your feelings. // but if it fails again, then report the first error. publish([arg], er || true, cb) }) } // report the unpublish error if this was a retry and unpublish failed if (er && isRetry && isRetry !== true) return cb(isRetry) if (er) return cb(er) console.log("+ " + data._id) cb() }) }) } function needVersion(er, data) { return er ? er : (data && !data.version) ? new Error("No version provided") : null }