This repository has been archived by the owner on Aug 11, 2022. It is now read-only.
/
read-json.js
311 lines (292 loc) · 9.57 KB
/
read-json.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
module.exports = readJson
readJson.processJson = processJson
readJson.unParsePeople = unParsePeople
readJson.parsePeople = parsePeople
var fs = require("./graceful-fs")
, semver = require("semver")
, path = require("path")
, log = require("./log")
, npm = require("../../npm")
, cache = {}
, timers = {}
, loadPackageDefaults = require("./load-package-defaults")
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]
}
}
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/)
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) {
var e = new Error(
"Failed to parse json\n"+ex.message+"\n"+jsonString)
if (cb) return cb(e)
throw e
}
return processObject(opts, cb)(er, json)
}}
function processObject (opts, cb) { return function (er, json) {
if (json.overlay) {
;["node", "npm"].forEach(function (k) {
if (!json.overlay[k]) return undefined
for (var i in json.overlay[k]) json[i] = json.overlay[k][i]
})
}
// 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.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 tag = opts.tag
if (tag) json.version = tag//+"-"+json.version
if (json.modules) {
if (typeof json.modules !== "object") {
var e = new Error("Invalid modules object")
if (cb) return cb(e)
throw e
}
Object.keys(json.modules).forEach(function (mod) {
if (typeof json.modules[mod] !== "string") {
var e = new Error("Invalid module "+mod+", not a string: "
+JSON.stringify(json.modules[mod]))
if (cb) return cb(e)
throw e
}
})
}
if (opts.wscript) {
var scripts = json.scripts = json.scripts || {}
if (!scripts.install && !scripts.preinstall) {
// don't fail if it was unexpected, just try.
scripts.preinstall = "node-waf clean || true; node-waf configure build"
}
}
if (!(semver.valid(json.version))) {
var e = new Error("Invalid version: "+json.version+"\n"
+"Must be X.Y.Z, with an optional trailing tag.\n"
+"See the section on 'version' in `npm help json`")
if (cb) return cb(e)
throw e
}
json.version = semver.clean(json.version)
if (json.bin && typeof json.bin === "string") {
var b = {}
b[ path.basename( json.bin ) ] = json.bin
json.bin = b
}
if (json["dev-dependencies"] && !json.devDependencies) {
json.devDependencies = json["dev-dependencies"]
delete json["dev-dependencies"]
}
;["dependencies", "devDependencies"].forEach(function (d) {
if (!json[d]) return
json[d] = depObjectify(json[d])
})
json._id = json.name+"@"+json.version
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.
log.silly(json, "testEngine")
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
}