summaryrefslogtreecommitdiff
path: root/deps/npm/lib/utils/read-json.js
diff options
context:
space:
mode:
Diffstat (limited to 'deps/npm/lib/utils/read-json.js')
-rw-r--r--deps/npm/lib/utils/read-json.js496
1 files changed, 496 insertions, 0 deletions
diff --git a/deps/npm/lib/utils/read-json.js b/deps/npm/lib/utils/read-json.js
new file mode 100644
index 0000000000..e71128c1b1
--- /dev/null
+++ b/deps/npm/lib/utils/read-json.js
@@ -0,0 +1,496 @@
+
+module.exports = readJson
+readJson.processJson = processJson
+readJson.unParsePeople = unParsePeople
+readJson.parsePeople = parsePeople
+readJson.clearCache = clearCache
+
+var fs = require("graceful-fs")
+ , semver = require("semver")
+ , path = require("path")
+ , log = require("./log.js")
+ , npm = require("../npm.js")
+ , cache = {}
+ , timers = {}
+ , loadPackageDefaults = require("./load-package-defaults.js")
+
+function readJson (jsonFile, opts, cb) {
+ if (typeof cb !== "function") cb = opts, opts = {}
+ if (cache.hasOwnProperty(jsonFile)) {
+ log.verbose(jsonFile, "from cache")
+ return cb(null, cache[jsonFile])
+ }
+ opts.file = jsonFile
+ if (!opts.tag) {
+ var parsedPath = jsonFile.indexOf(npm.dir) === 0 && jsonFile.match(
+ /\/([^\/]+)\/([^\/]+)\/package\/package\.json$/)
+ if (parsedPath && semver.valid(parsedPath[2])) {
+ // this is a package.json in some installed package.
+ // infer the opts.tag so that linked packages behave right.
+ opts.tag = parsedPath[2]
+ }
+ }
+
+ var wscript = null
+ , contributors = null
+ , serverjs = null
+
+ if (opts.wscript != null) {
+ wscript = opts.wscript
+ next()
+ } else fs.readFile( path.join(path.dirname(jsonFile), "wscript")
+ , function (er, data) {
+ if (er) opts.wscript = false
+ else opts.wscript = !!(data.toString().match(/(^|\n)def build\b/)
+ && data.toString().match(/(^|\n)def configure\b/))
+ wscript = opts.wscript
+ next()
+ })
+
+ if (opts.contributors != null) {
+ contributors = opts.contributors
+ next()
+ } else fs.readFile( path.join(path.dirname(jsonFile), "AUTHORS")
+ , function (er, data) {
+ if (er) opts.contributors = false
+ else {
+ data = data.toString().split(/\r?\n/).map(function (l) {
+ l = l.trim().split("#").shift()
+ return l
+ }).filter(function (l) { return l })
+ opts.contributors = data
+ }
+ contributors = opts.contributors
+ next()
+ })
+
+ if (opts.serverjs != null) {
+ serverjs = opts.serverjs
+ next()
+ } else fs.stat( path.join(path.dirname(jsonFile), "server.js")
+ , function (er, st) {
+ if (er) opts.serverjs = false
+ else opts.serverjs = st.isFile()
+ serverjs = opts.serverjs
+ next()
+ })
+
+ function next () {
+ if (wscript === null
+ || contributors === null
+ || serverjs === null) {
+ return
+ }
+
+ fs.readFile(jsonFile, processJson(opts, function (er, data) {
+ if (er) return cb(er)
+ var doLoad = !(jsonFile.indexOf(npm.cache) === 0 &&
+ path.basename(path.dirname(jsonFile)) !== "package")
+ if (!doLoad) return cb(er, data)
+ loadPackageDefaults(data, path.dirname(jsonFile), cb)
+ }))
+ }
+}
+
+function processJson (opts, cb) {
+ if (typeof cb !== "function") cb = opts, opts = {}
+ if (typeof cb !== "function") {
+ var thing = cb, cb = null
+ return P(null, thing)
+ } else return P
+
+ function P (er, thing) {
+ if (er) {
+ if (cb) return cb(er, thing)
+ throw er
+ }
+ if (typeof thing === "object" && !Buffer.isBuffer(thing)) {
+ return processObject(opts, cb)(er, thing)
+ } else {
+ return processJsonString(opts, cb)(er, thing)
+ }
+ }
+}
+
+function processJsonString (opts, cb) { return function (er, jsonString) {
+ jsonString += ""
+ if (er) return cb(er, jsonString)
+ var json
+ try {
+ json = JSON.parse(jsonString)
+ } catch (ex) {
+ if (opts.file && opts.file.indexOf(npm.dir) === 0) {
+ try {
+ json = require("vm").runInNewContext("(\n"+jsonString+"\n)")
+ log.error(opts.file, "Error parsing json")
+ log.error(ex, "parse error ")
+ } catch (ex2) {
+ return jsonParseFail(ex, opts.file, cb)
+ }
+ } else {
+ return jsonParseFail(ex, opts.file, cb)
+ }
+ }
+ return processObject(opts, cb)(er, json)
+}}
+
+
+function jsonParseFail (ex, file, cb) {
+ var e = new Error(
+ "Failed to parse json\n"+ex.message)
+ e.errno = npm.EJSONPARSE
+ e.file = file
+ if (cb) return cb(e)
+ throw e
+}
+
+// a warning for deprecated or likely-incorrect fields
+var typoWarned = {}
+function typoWarn (json) {
+ if (typoWarned[json._id]) return
+ typoWarned[json._id] = true
+
+ if (json.modules) {
+ log.warn("package.json: 'modules' object is deprecated", json._id)
+ delete json.modules
+ }
+
+ // http://registry.npmjs.org/-/fields
+ var typos = { "dependancies": "dependencies"
+ , "dependecies": "dependencies"
+ , "depdenencies": "dependencies"
+ , "devEependencies": "devDependencies"
+ , "depends": "dependencies"
+ , "devDependences": "devDependencies"
+ , "devDepenencies": "devDependencies"
+ , "devdependencies": "devDependencies"
+ , "repostitory": "repository"
+ , "prefereGlobal": "preferGlobal"
+ , "hompage": "homepage"
+ , "hampage": "homepage" // XXX maybe not a typo, just delicious?
+ , "autohr": "author"
+ , "autor": "author"
+ , "contributers": "contributors"
+ , "publicationConfig": "publishConfig"
+ }
+
+ Object.keys(typos).forEach(function (d) {
+ if (json.hasOwnProperty(d)) {
+ log.warn( "package.json: '" + d + "' should probably be '"
+ + typos[d] + "'", json._id)
+ }
+ })
+
+ // bugs typos
+ var bugsTypos = { "web": "url"
+ , "name": "url"
+ }
+
+ if (typeof json.bugs === "object") {
+ Object.keys(bugsTypos).forEach(function (d) {
+ if (json.bugs.hasOwnProperty(d)) {
+ log.warn( "package.json: bugs['" + d + "'] should probably be "
+ + "bugs['" + bugsTypos[d] + "']", json._id)
+ }
+ })
+ }
+
+ // script typos
+ var scriptTypos = { "server": "start" }
+ if (json.scripts) Object.keys(scriptTypos).forEach(function (d) {
+ if (json.scripts.hasOwnProperty(d)) {
+ log.warn( "package.json: scripts['" + d + "'] should probably be "
+ + "scripts['" + scriptTypos[d] + "']", json._id)
+ }
+ })
+}
+
+
+function processObject (opts, cb) { return function (er, json) {
+ // json._npmJsonOpts = opts
+ // log.warn(json, "processing json")
+ if (npm.config.get("username")) {
+ json._npmUser = { name: npm.config.get("username")
+ , email: npm.config.get("email") }
+ }
+
+ // slashes would be a security risk.
+ // anything else will just fail harmlessly.
+ if (!json.name) {
+ var e = new Error("No 'name' field found in package.json")
+ if (cb) return cb(e)
+ throw e
+ }
+ json.name = json.name.trim()
+ if (json.name.charAt(0) === "." || json.name.match(/[\/@\s\+%:]/)) {
+ var msg = "Invalid name: "
+ + JSON.stringify(json.name)
+ + " may not start with '.' or contain %/@+: or whitespace"
+ , e = new Error(msg)
+ if (cb) return cb(e)
+ throw e
+ }
+ if (json.name.toLowerCase() === "node_modules") {
+ var msg = "Invalid package name: node_modules"
+ , e = new Error(msg)
+ if (cb) return cb(e)
+ throw e
+ }
+ if (json.name.toLowerCase() === "favicon.ico") {
+ var msg = "Sorry, favicon.ico is a picture, not a package."
+ , e = new Error(msg)
+ if (cb) return cb(e)
+ throw e
+ }
+
+ if (json.repostories) {
+ var msg = "'repositories' (plural) No longer supported.\n"
+ + "Please pick one, and put it in the 'repository' field."
+ , e = new Error(msg)
+ // uncomment once this is no longer an issue.
+ // if (cb) return cb(e)
+ // throw e
+ log.error(msg, "incorrect json: "+json.name)
+ json.repostory = json.repositories[0]
+ delete json.repositories
+ }
+
+ if (json.repository) {
+ if (typeof json.repository === "string") {
+ json.repository = { type : "git"
+ , url : json.repository }
+ }
+ var repo = json.repository.url || ""
+ repo = repo.replace(/^(https?|git):\/\/[^\@]+\@github.com/
+ ,'$1://github.com')
+ if (json.repository.type === "git"
+ && ( repo.match(/^https?:\/\/github.com/)
+ || repo.match(/github.com\/[^\/]+\/[^\/]+\/?$/)
+ && !repo.match(/\.git$/)
+ )) {
+ repo = repo.replace(/^https?:\/\/github.com/, 'git://github.com')
+ if (!repo.match(/\.git$/)) {
+ repo = repo.replace(/\/?$/, '.git')
+ }
+ }
+ if (repo.match(/github\.com\/[^\/]+\/[^\/]+\/?$/)
+ && repo.match(/\.git\.git$/)) {
+ log.warn(repo, "Probably broken git url")
+ }
+ json.repository.url = repo
+ }
+
+ var files = json.files
+ if (files && !Array.isArray(files)) {
+ log.warn(files, "Invalid 'files' member. See 'npm help json'")
+ delete json.files
+ }
+
+ var kw = json.keywords
+ if (typeof kw === "string") {
+ kw = kw.split(/,\s+/)
+ json.keywords = kw
+ }
+
+ json._id = json.name+"@"+json.version
+
+ var tag = opts.tag
+ if (tag) json.version = tag
+
+ var scripts = json.scripts || {}
+
+ // if it has a wscript, then build it.
+ if (opts.wscript && !json.prebuilt) {
+ log.verbose([json.prebuilt, opts], "has wscript")
+ if (!scripts.install && !scripts.preinstall) {
+ // don't fail if it was unexpected, just try.
+ scripts.preinstall = "node-waf clean || true; node-waf configure build"
+ json.scripts = scripts
+ }
+ }
+
+ // if it has an AUTHORS, then credit them
+ if (opts.contributors && Array.isArray(opts.contributors)
+ && opts.contributors.length) {
+ json.contributors = opts.contributors
+ }
+
+ // if it has a server.js, then start it.
+ if (opts.serverjs && !scripts.start) {
+ scripts.start = "node server.js"
+ json.scripts = scripts
+ }
+
+ if (!(semver.valid(json.version))) {
+ var m
+ if (!json.version) {
+ m = "'version' field missing\n"
+ } else {
+ m = "Invalid 'version' field: "+json.version+"\n"
+ }
+
+ m += "'version' Must be X.Y.Z, with an optional trailing tag.\n"
+ + "See the section on 'version' in `npm help json`"
+
+ var e = new Error(m)
+ if (cb) return cb(e)
+ throw e
+ }
+ json.version = semver.clean(json.version)
+
+ if (json.bin && typeof json.bin === "string") {
+ var b = {}
+ b[ json.name ] = json.bin
+ json.bin = b
+ }
+
+ if (json.bundledDependencies && !json.bundleDependencies) {
+ json.bundleDependencies = json.bundledDependencies
+ delete json.bundledDependencies
+ }
+
+ if (json.bundleDependencies && !Array.isArray(json.bundleDependencies)) {
+ var e = new Error("bundleDependencies must be an array.\n"
+ +"See `npm help json`")
+ if (cb) return cb(e)
+ throw e
+ }
+
+ if (json["dev-dependencies"] && !json.devDependencies) {
+ json.devDependencies = json["dev-dependencies"]
+ delete json["dev-dependencies"]
+ }
+
+ ;["dependencies", "devDependencies"].forEach(function (d) {
+ json[d] = json[d] ? depObjectify(json[d]) : {}
+ })
+
+ if (opts.dev || npm.config.get("dev") || npm.config.get("npat")) {
+ // log.warn(json._id, "Adding devdeps")
+ Object.keys(json.devDependencies || {}).forEach(function (d) {
+ json.dependencies[d] = json.devDependencies[d]
+ })
+ // log.warn(json.dependencies, "Added devdeps")
+ }
+
+ typoWarn(json)
+
+ json = testEngine(json)
+ json = parsePeople(unParsePeople(json))
+ if ( json.bugs ) json.bugs = parsePerson(unParsePerson(json.bugs))
+ json._npmVersion = npm.version
+ json._nodeVersion = process.version
+ if (opts.file) {
+ log.verbose(opts.file, "caching")
+ cache[opts.file] = json
+ // arbitrary
+ var keys = Object.keys(cache)
+ , l = keys.length
+ if (l > 10000) for (var i = 0; i < l - 5000; i ++) {
+ delete cache[keys[i]]
+ }
+ }
+ if (cb) cb(null,json)
+ return json
+}}
+
+function depObjectify (deps) {
+ if (!Array.isArray(deps)) return deps
+ var o = {}
+ deps.forEach(function (d) {
+ d = d.trim().split(/(:?[@\s><=])/)
+ o[d.shift()] = d.join("").trim().replace(/^@/, "")
+ })
+ return o
+}
+
+function testEngine (json) {
+ // if engines is empty, then assume that node is allowed.
+ if ( !json.engines
+ || Array.isArray(json.engines)
+ && !json.engines.length
+ || typeof json.engines === "object"
+ && !Object.keys(json.engines).length
+ ) {
+ json.engines = { "node" : "*" }
+ }
+ if (typeof json.engines === "string") {
+ if (semver.validRange(json.engines) !== null) {
+ json.engines = { "node" : json.engines }
+ } else json.engines = [ json.engines ]
+ }
+
+ var nodeVer = npm.config.get("node-version")
+ , ok = false
+ if (nodeVer) nodeVer = nodeVer.replace(/\+$/, '')
+ if (Array.isArray(json.engines)) {
+ // Packages/1.0 commonjs style, with an array.
+ // hack it to just hang a "node" member with the version range,
+ // then do the npm-style check below.
+ for (var i = 0, l = json.engines.length; i < l; i ++) {
+ var e = json.engines[i].trim()
+ if (e.substr(0, 4) === "node") {
+ json.engines.node = e.substr(4)
+ } else if (e.substr(0, 3) === "npm") {
+ json.engines.npm = e.substr(3)
+ }
+ }
+ }
+ if (json.engines.node === "") json.engines.node = "*"
+ if (json.engines.node && null === semver.validRange(json.engines.node)) {
+ log.warn( json.engines.node
+ , "Invalid range in engines.node. Please see `npm help json`" )
+ }
+
+ if (nodeVer) {
+ json._engineSupported = semver.satisfies( nodeVer
+ , json.engines.node || "null" )
+ }
+ if (json.engines.hasOwnProperty("npm") && json._engineSupported) {
+ json._engineSupported = semver.satisfies(npm.version, json.engines.npm)
+ }
+ return json
+}
+
+function unParsePeople (json) { return parsePeople(json, true) }
+
+function parsePeople (json, un) {
+ var fn = un ? unParsePerson : parsePerson
+ if (json.author) json.author = fn(json.author)
+ ;["maintainers", "contributors"].forEach(function (set) {
+ if (Array.isArray(json[set])) json[set] = json[set].map(fn)
+ })
+ return json
+}
+
+function unParsePerson (person) {
+ if (typeof person === "string") return person
+ var name = person.name || ""
+ , u = person.url || person.web
+ , url = u ? (" ("+u+")") : ""
+ , e = person.email || person.mail
+ , email = e ? (" <"+e+">") : ""
+ return name+email+url
+}
+
+function parsePerson (person) {
+ if (typeof person !== "string") return person
+ var name = person.match(/^([^\(<]+)/)
+ , url = person.match(/\(([^\)]+)\)/)
+ , email = person.match(/<([^>]+)>/)
+ , obj = {}
+ if (name && name[0].trim()) obj.name = name[0].trim()
+ if (email) obj.email = email[1]
+ if (url) obj.url = url[1]
+ return obj
+}
+
+function clearCache (prefix) {
+ if (!prefix) {
+ cache = {}
+ return
+ }
+ Object.keys(cache).forEach(function (c) {
+ if (c.indexOf(prefix) === 0) delete cache[c]
+ })
+}