lx-scan
Version:
License eXtension to find 5-tuples of all installed packages: name, version, project home page, license (e. g. Apache v2, BSD) and required notice. It includes a GUI to edit information for each package and to enter information if necessary.
314 lines (257 loc) • 8.54 kB
JavaScript
var path = require("path"),
asyncMap = require("slide").asyncMap,
semver = require("semver"),
readJson = require("read-package-json"),
url = require("url"),
npa = require("npm-package-arg")
try {
var fs = require("graceful-fs")
} catch (er) {
var fs = require("fs")
}
var scanner = require("./scanner_proto.js");
function lx (lxopts, cb) {
var lxpackages = []
var prefix = lxopts.prefix
readLic(prefix, null, null, null, 0, {"dev": true}, function (er, obj ) {
if (er) return cb(er)
// now obj has all the installed things, where they're installed
// figure out the inheritance links, now that the object is built.
// resolveInheritance(obj, opts)
// markExtraneous(obj)
var objs = []
obj = makeLicInfo(obj, null, null, null, null, lxpackages);
//console.log(lxpackages)
var seen = {}
lxpackages.forEach(
function(pkg, cb){
// FIXME: The "version" tag here is obtained in a rather dirty way; what really needs to happen is that the version and name
// attributes should be generated and labelled separated during the intial scan sweep
var obj = {"name":pkg.name, "label": pkg.label, "version": pkg.label.split("@")[1], "licensefile": pkg.licensefile}
if(seen[pkg.label] == undefined) seen[pkg.label] = 1
else {
seen[pkg.label]++
//console.error("seen",pkg.label,seen[pkg.label])
}
if(pkg.licensefile.length == 0 && pkg.repository && seen[pkg.label] == 1) {
var a = pkg.repository
obj["repository"] = a
if (obj.homepage == undefined) obj["homepage"] = a
objs.push(obj)
} else if(seen[pkg.label] == 1){
if(pkg.license) obj["license"]= pkg.license
if(pkg.repository) obj["repository"] = pkg.repository
if(pkg.homepage) obj["homepage"] = pkg.homepage
else if(pkg.repository) obj["homepage"] = pkg.repository
objs.push(obj)
}
if(pkg.licensefile) obj["licensefile"]= pkg.licensefile
}
)
cb(null,objs);
})
}
var rpSeen = {}
function readLic(folder, parent, name, reqver, depth, opts, cb) {
var installed
, obj
, real
, link
fs.readdir(path.resolve(folder, "node_modules"), function (er, i) {
// error indicates that nothing is installed here
if (er) i = []
installed = i.filter(function (f) { return f.charAt(0) !== "." })
next()
})
readJson(path.resolve(folder, "package.json"), function (er, data) {
obj = copy(data)
if (!parent) {
obj = obj || true
er = null
}
return next(er)
})
fs.lstat(folder, function (er, st) {
if (er) {
if (!parent) real = true
return next(er)
}
fs.realpath(folder, function (er, rp) {
//console.error("realpath(%j) = %j", folder, rp)
real = rp
if (st.isSymbolicLink()) link = rp
next(er)
})
})
var errState = null
, called = false
function next (er) {
if (errState) return
if (er) {
errState = er
return cb(null, [])
}
//console.error('next', installed, obj && typeof obj, name, real)
if (!installed || !obj || !real || called) return
called = true
if (rpSeen[real]) return cb(null, rpSeen[real])
if (obj === true) {
obj = {dependencies:{}, path:folder}
installed.forEach(function (i) { obj.dependencies[i] = "*" })
}
if (name && obj.name !== name) obj.invalid = true
obj.realName = name || obj.name
obj.dependencies = obj.dependencies || {}
// "foo":"http://blah" and "foo":"latest" are always presumed valid
if (reqver
&& semver.validRange(reqver, true)
&& !semver.satisfies(obj.version, reqver, true)) {
obj.invalid = true
}
if (parent) {
var deps = parent.dependencies || {}
var inDeps = name in deps
var devDeps = parent.devDependencies || {}
var inDev = opts.dev && (name in devDeps)
if (!inDeps && !inDev) {
obj.extraneous = true
}
}
obj.path = obj.path || folder
obj.realPath = real
obj.link = link
if (parent && !obj.link) obj.parent = parent
rpSeen[real] = obj
obj.depth = depth
//if (depth >= opts.depth) return cb(null, obj)
asyncMap(installed, function (pkg, cb) {
var rv = obj.dependencies[pkg]
if (!rv && obj.devDependencies && opts.dev)
rv = obj.devDependencies[pkg]
if (depth >= opts.depth) {
// just try to get the version number
var pkgfolder = path.resolve(folder, "node_modules", pkg)
, jsonFile = path.resolve(pkgfolder, "package.json")
return readJson(jsonFile, function (er, depData) {
// already out of our depth, ignore errors
if (er || !depData || !depData.version) return cb(null, obj)
if (depth === opts.depth) {
// edge case, ignore dependencies
depData.dependencies = {}
depData.peerDependencies = {}
obj.dependencies[pkg] = depData
} else {
obj.dependencies[pkg] = depData.version
}
cb(null, obj)
})
}
//wjs console.log(folder)
readLic( path.resolve(folder, "node_modules/"+pkg)
, obj, pkg, obj.dependencies[pkg], depth + 1, opts
, cb )
}, function (er, installedData) {
if (er) return cb(er)
installedData.forEach(function (dep) {
obj.dependencies[dep.realName] = dep
})
return cb(null, obj)
})
}
}
function copy (obj) {
if (!obj || typeof obj !== 'object') return obj
if (Array.isArray(obj)) return obj.map(copy)
var o = {}
for (var i in obj) o[i] = copy(obj[i])
return o
}
function makeLicInfo (data, dir, depth, parent, d, lxpackages) {
if (typeof data === "string") {
// if (depth < npm.config.get("depth")) {
// just missing
// var p = parent.link || parent.path
// var unmet = "UNMET DEPENDENCY"
// }
return data
}
var out = {}
// the top level is a bit special.
out.label = data._id || ""
if (data._found === true && data._id) {
out.label = out.label.trim()
}
if (data.link) out.label += " -> " + data.link
if (data.invalid) {
if (data.realName !== data.name) out.label += " ("+data.realName+")"
out.label += " " + "invalid"
}
if (data.peerInvalid) {
out.label += " " + "peer invalid"
}
if (data.extraneous && data.path !== dir) {
out.label += " " + "extraneous"
}
// add giturl to name@version
if (data._resolved) {
var p = url.parse(data._resolved)
if (npa(data._resolved).type === "git")
out.label += " (" + data._resolved + ")"
}
if (data.name) {
out.name=data.name
var str = ""
var files = []
if(data.parent != null) files = fs.readdirSync(data.path)
data.licensefile = []
files.forEach(function (i){
var aa = []
if (i.match(/licen[cs]/i)) {
str = str + data.path + "/" + i
data.licensefile.push({"licensepath":str,"notice": undefined})
} else if (i.match(/^readme/i) && data.licensefile.length == 0 ) {
str = data.path + "/" + i
data.licensefile.push({"licensepath":str,"notice": undefined})
// console.log("no license file but " + str)
}
str = ""
})
}
if (data.version) out.version=data.version
if (data.description) out.description=data.description
if (data.repository) out.repository=data.repository.url
if (data.homepage) out.homepage=data.homepage
// if (data.license) out.license=data.license
if (data.licensefile) {
var i = 0;
out.licensefile=data.licensefile
out.licensefile.forEach(function(lic) {
if(fs.lstatSync(lic.licensepath).isFile()) {
var str = fs.readFileSync(lic.licensepath,'utf8')
if (aa = str.match(/(copyright.+)/i)) {
// lic.notice = aa[0]
lic.text = str
i++}
else {}
}
})
if(i == 0) out.licensefile = []
}
if(out.label != '') lxpackages.push(out)
// now all the children.
out.nodes = Object.keys(data.dependencies || {})
.sort(alphasort).map(function (d) {
return makeLicInfo(data.dependencies[d], dir, depth + 1, data, d, lxpackages)
})
if (out.nodes.length === 0 && data.path === dir) {
out.nodes = ["(empty)"]
}
return out
}
function alphasort (a, b) {
a = a.toLowerCase()
b = b.toLowerCase()
return a > b ? 1
: a < b ? -1 : 0
}
module.exports = new scanner({scanner_function: lx});