|
|
|
|
|
|
|
module.exports = publish
|
|
|
|
|
|
|
|
var npm = require("./npm.js")
|
|
|
|
, log = require("npmlog")
|
|
|
|
, path = require("path")
|
|
|
|
, readJson = require("read-package-json")
|
|
|
|
, lifecycle = require("./utils/lifecycle.js")
|
|
|
|
, chain = require("slide").chain
|
|
|
|
, mapToRegistry = require("./utils/map-to-registry.js")
|
|
|
|
, cachedPackageRoot = require("./cache/cached-package-root.js")
|
|
|
|
, createReadStream = require("graceful-fs").createReadStream
|
|
|
|
, npa = require("npm-package-arg")
|
|
|
|
, semver = require('semver')
|
|
|
|
, getPublishConfig = require("./utils/get-publish-config.js")
|
|
|
|
|
|
|
|
publish.usage = "npm publish <tarball> [--tag <tagname>]"
|
|
|
|
+ "\nnpm publish <folder> [--tag <tagname>]"
|
|
|
|
+ "\n\nPublishes '.' if no argument supplied"
|
|
|
|
+ "\n\nSets tag `latest` if no --tag specified"
|
|
|
|
|
|
|
|
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 t = npm.config.get('tag').trim()
|
|
|
|
if (semver.validRange(t)) {
|
|
|
|
var er = new Error("Tag name must not be a valid SemVer range: " + t)
|
|
|
|
return cb(er)
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
|
|
|
if (er && er.code !== "ENOENT" && er.code !== "ENOTDIR") return cb(er)
|
|
|
|
|
|
|
|
if (data) {
|
|
|
|
if (!data.name) return cb(new Error("No name provided"))
|
|
|
|
if (!data.version) return cb(new Error("No version provided"))
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
else 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(cachedPackageRoot(data), "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 mappedConfig = getPublishConfig(
|
|
|
|
data.publishConfig,
|
|
|
|
npm.config,
|
|
|
|
npm.registry
|
|
|
|
)
|
|
|
|
var config = mappedConfig.config
|
|
|
|
var registry = mappedConfig.client
|
|
|
|
|
|
|
|
data._npmVersion = npm.version
|
|
|
|
data._nodeVersion = process.versions.node
|
|
|
|
|
|
|
|
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, auth, registryBase) {
|
|
|
|
if (er) return cb(er)
|
|
|
|
|
|
|
|
var tarballPath = cachedir + ".tgz"
|
|
|
|
|
|
|
|
// we just want the base registry URL in this case
|
|
|
|
log.verbose("publish", "registryBase", registryBase)
|
|
|
|
log.silly("publish", "uploading", tarballPath)
|
|
|
|
|
|
|
|
data._npmUser = {
|
|
|
|
name : auth.username,
|
|
|
|
email : auth.email
|
|
|
|
}
|
|
|
|
|
|
|
|
var params = {
|
|
|
|
metadata : data,
|
|
|
|
body : createReadStream(tarballPath),
|
|
|
|
auth : auth
|
|
|
|
}
|
|
|
|
|
|
|
|
// registry-frontdoor cares about the access level, which is only
|
|
|
|
// configurable for scoped packages
|
|
|
|
if (config.get("access")) {
|
|
|
|
if (!npa(data.name).scope && config.get("access") === "restricted") {
|
|
|
|
return cb(new Error("Can't restrict access to unscoped packages."))
|
|
|
|
}
|
|
|
|
|
|
|
|
params.access = config.get("access")
|
|
|
|
}
|
|
|
|
|
|
|
|
registry.publish(registryBase, params, 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()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|