| var semver = require("semver") | |
| var validateLicense = require('validate-npm-package-license'); | |
| var hostedGitInfo = require("hosted-git-info") | |
| var isBuiltinModule = require("resolve").isCore | |
| var depTypes = ["dependencies","devDependencies","optionalDependencies"] | |
| var extractDescription = require("./extract_description") | |
| var url = require("url") | |
| var typos = require("./typos.json") | |
| var fixer = module.exports = { | |
| // default warning function | |
| warn: function() {}, | |
| fixRepositoryField: function(data) { | |
| if (data.repositories) { | |
| this.warn("repositories"); | |
| data.repository = data.repositories[0] | |
| } | |
| if (!data.repository) return this.warn("missingRepository") | |
| if (typeof data.repository === "string") { | |
| data.repository = { | |
| type: "git", | |
| url: data.repository | |
| } | |
| } | |
| var r = data.repository.url || "" | |
| if (r) { | |
| var hosted = hostedGitInfo.fromUrl(r) | |
| if (hosted) { | |
| r = data.repository.url | |
| = hosted.getDefaultRepresentation() == "shortcut" ? hosted.https() : hosted.toString() | |
| } | |
| } | |
| if (r.match(/github.com\/[^\/]+\/[^\/]+\.git\.git$/)) { | |
| this.warn("brokenGitUrl", r) | |
| } | |
| } | |
| , fixTypos: function(data) { | |
| Object.keys(typos.topLevel).forEach(function (d) { | |
| if (data.hasOwnProperty(d)) { | |
| this.warn("typo", d, typos.topLevel[d]) | |
| } | |
| }, this) | |
| } | |
| , fixScriptsField: function(data) { | |
| if (!data.scripts) return | |
| if (typeof data.scripts !== "object") { | |
| this.warn("nonObjectScripts") | |
| delete data.scripts | |
| return | |
| } | |
| Object.keys(data.scripts).forEach(function (k) { | |
| if (typeof data.scripts[k] !== "string") { | |
| this.warn("nonStringScript") | |
| delete data.scripts[k] | |
| } else if (typos.script[k] && !data.scripts[typos.script[k]]) { | |
| this.warn("typo", k, typos.script[k], "scripts") | |
| } | |
| }, this) | |
| } | |
| , fixFilesField: function(data) { | |
| var files = data.files | |
| if (files && !Array.isArray(files)) { | |
| this.warn("nonArrayFiles") | |
| delete data.files | |
| } else if (data.files) { | |
| data.files = data.files.filter(function(file) { | |
| if (!file || typeof file !== "string") { | |
| this.warn("invalidFilename", file) | |
| return false | |
| } else { | |
| return true | |
| } | |
| }, this) | |
| } | |
| } | |
| , fixBinField: function(data) { | |
| if (!data.bin) return; | |
| if (typeof data.bin === "string") { | |
| var b = {} | |
| var match | |
| if (match = data.name.match(/^@[^/]+[/](.*)$/)) { | |
| b[match[1]] = data.bin | |
| } else { | |
| b[data.name] = data.bin | |
| } | |
| data.bin = b | |
| } | |
| } | |
| , fixManField: function(data) { | |
| if (!data.man) return; | |
| if (typeof data.man === "string") { | |
| data.man = [ data.man ] | |
| } | |
| } | |
| , fixBundleDependenciesField: function(data) { | |
| var bdd = "bundledDependencies" | |
| var bd = "bundleDependencies" | |
| if (data[bdd] && !data[bd]) { | |
| data[bd] = data[bdd] | |
| delete data[bdd] | |
| } | |
| if (data[bd] && !Array.isArray(data[bd])) { | |
| this.warn("nonArrayBundleDependencies") | |
| delete data[bd] | |
| } else if (data[bd]) { | |
| data[bd] = data[bd].filter(function(bd) { | |
| if (!bd || typeof bd !== 'string') { | |
| this.warn("nonStringBundleDependency", bd) | |
| return false | |
| } else { | |
| if (!data.dependencies) { | |
| data.dependencies = {} | |
| } | |
| if (!data.dependencies.hasOwnProperty(bd)) { | |
| this.warn("nonDependencyBundleDependency", bd) | |
| data.dependencies[bd] = "*" | |
| } | |
| return true | |
| } | |
| }, this) | |
| } | |
| } | |
| , fixDependencies: function(data, strict) { | |
| var loose = !strict | |
| objectifyDeps(data, this.warn) | |
| addOptionalDepsToDeps(data, this.warn) | |
| this.fixBundleDependenciesField(data) | |
| ;['dependencies','devDependencies'].forEach(function(deps) { | |
| if (!(deps in data)) return | |
| if (!data[deps] || typeof data[deps] !== "object") { | |
| this.warn("nonObjectDependencies", deps) | |
| delete data[deps] | |
| return | |
| } | |
| Object.keys(data[deps]).forEach(function (d) { | |
| var r = data[deps][d] | |
| if (typeof r !== 'string') { | |
| this.warn("nonStringDependency", d, JSON.stringify(r)) | |
| delete data[deps][d] | |
| } | |
| var hosted = hostedGitInfo.fromUrl(data[deps][d]) | |
| if (hosted) data[deps][d] = hosted.toString() | |
| }, this) | |
| }, this) | |
| } | |
| , fixModulesField: function (data) { | |
| if (data.modules) { | |
| this.warn("deprecatedModules") | |
| delete data.modules | |
| } | |
| } | |
| , fixKeywordsField: function (data) { | |
| if (typeof data.keywords === "string") { | |
| data.keywords = data.keywords.split(/,\s+/) | |
| } | |
| if (data.keywords && !Array.isArray(data.keywords)) { | |
| delete data.keywords | |
| this.warn("nonArrayKeywords") | |
| } else if (data.keywords) { | |
| data.keywords = data.keywords.filter(function(kw) { | |
| if (typeof kw !== "string" || !kw) { | |
| this.warn("nonStringKeyword"); | |
| return false | |
| } else { | |
| return true | |
| } | |
| }, this) | |
| } | |
| } | |
| , fixVersionField: function(data, strict) { | |
| // allow "loose" semver 1.0 versions in non-strict mode | |
| // enforce strict semver 2.0 compliance in strict mode | |
| var loose = !strict | |
| if (!data.version) { | |
| data.version = "" | |
| return true | |
| } | |
| if (!semver.valid(data.version, loose)) { | |
| throw new Error('Invalid version: "'+ data.version + '"') | |
| } | |
| data.version = semver.clean(data.version, loose) | |
| return true | |
| } | |
| , fixPeople: function(data) { | |
| modifyPeople(data, unParsePerson) | |
| modifyPeople(data, parsePerson) | |
| } | |
| , fixNameField: function(data, options) { | |
| if (typeof options === "boolean") options = {strict: options} | |
| else if (typeof options === "undefined") options = {} | |
| var strict = options.strict | |
| if (!data.name && !strict) { | |
| data.name = "" | |
| return | |
| } | |
| if (typeof data.name !== "string") { | |
| throw new Error("name field must be a string.") | |
| } | |
| if (!strict) | |
| data.name = data.name.trim() | |
| ensureValidName(data.name, strict, options.allowLegacyCase) | |
| if (isBuiltinModule(data.name)) | |
| this.warn("conflictingName", data.name) | |
| } | |
| , fixDescriptionField: function (data) { | |
| if (data.description && typeof data.description !== 'string') { | |
| this.warn("nonStringDescription") | |
| delete data.description | |
| } | |
| if (data.readme && !data.description) | |
| data.description = extractDescription(data.readme) | |
| if(data.description === undefined) delete data.description; | |
| if (!data.description) this.warn("missingDescription") | |
| } | |
| , fixReadmeField: function (data) { | |
| if (!data.readme) { | |
| this.warn("missingReadme") | |
| data.readme = "ERROR: No README data found!" | |
| } | |
| } | |
| , fixBugsField: function(data) { | |
| if (!data.bugs && data.repository && data.repository.url) { | |
| var hosted = hostedGitInfo.fromUrl(data.repository.url) | |
| if(hosted && hosted.bugs()) { | |
| data.bugs = {url: hosted.bugs()} | |
| } | |
| } | |
| else if(data.bugs) { | |
| var emailRe = /^.+@.*\..+$/ | |
| if(typeof data.bugs == "string") { | |
| if(emailRe.test(data.bugs)) | |
| data.bugs = {email:data.bugs} | |
| else if(url.parse(data.bugs).protocol) | |
| data.bugs = {url: data.bugs} | |
| else | |
| this.warn("nonEmailUrlBugsString") | |
| } | |
| else { | |
| bugsTypos(data.bugs, this.warn) | |
| var oldBugs = data.bugs | |
| data.bugs = {} | |
| if(oldBugs.url) { | |
| if(typeof(oldBugs.url) == "string" && url.parse(oldBugs.url).protocol) | |
| data.bugs.url = oldBugs.url | |
| else | |
| this.warn("nonUrlBugsUrlField") | |
| } | |
| if(oldBugs.email) { | |
| if(typeof(oldBugs.email) == "string" && emailRe.test(oldBugs.email)) | |
| data.bugs.email = oldBugs.email | |
| else | |
| this.warn("nonEmailBugsEmailField") | |
| } | |
| } | |
| if(!data.bugs.email && !data.bugs.url) { | |
| delete data.bugs | |
| this.warn("emptyNormalizedBugs") | |
| } | |
| } | |
| } | |
| , fixHomepageField: function(data) { | |
| if (!data.homepage && data.repository && data.repository.url) { | |
| var hosted = hostedGitInfo.fromUrl(data.repository.url) | |
| if (hosted && hosted.docs()) data.homepage = hosted.docs() | |
| } | |
| if (!data.homepage) return | |
| if(typeof data.homepage !== "string") { | |
| this.warn("nonUrlHomepage") | |
| return delete data.homepage | |
| } | |
| if(!url.parse(data.homepage).protocol) { | |
| data.homepage = "http://" + data.homepage | |
| } | |
| } | |
| , fixLicenseField: function(data) { | |
| if (!data.license) { | |
| return this.warn("missingLicense") | |
| } else{ | |
| if ( | |
| typeof(data.license) !== 'string' || | |
| data.license.length < 1 || | |
| data.license.trim() === '' | |
| ) { | |
| this.warn("invalidLicense") | |
| } else { | |
| if (!validateLicense(data.license).validForNewPackages) | |
| this.warn("invalidLicense") | |
| } | |
| } | |
| } | |
| } | |
| function isValidScopedPackageName(spec) { | |
| if (spec.charAt(0) !== '@') return false | |
| var rest = spec.slice(1).split('/') | |
| if (rest.length !== 2) return false | |
| return rest[0] && rest[1] && | |
| rest[0] === encodeURIComponent(rest[0]) && | |
| rest[1] === encodeURIComponent(rest[1]) | |
| } | |
| function isCorrectlyEncodedName(spec) { | |
| return !spec.match(/[\/@\s\+%:]/) && | |
| spec === encodeURIComponent(spec) | |
| } | |
| function ensureValidName (name, strict, allowLegacyCase) { | |
| if (name.charAt(0) === "." || | |
| !(isValidScopedPackageName(name) || isCorrectlyEncodedName(name)) || | |
| (strict && (!allowLegacyCase) && name !== name.toLowerCase()) || | |
| name.toLowerCase() === "node_modules" || | |
| name.toLowerCase() === "favicon.ico") { | |
| throw new Error("Invalid name: " + JSON.stringify(name)) | |
| } | |
| } | |
| function modifyPeople (data, fn) { | |
| if (data.author) data.author = fn(data.author) | |
| ;["maintainers", "contributors"].forEach(function (set) { | |
| if (!Array.isArray(data[set])) return; | |
| data[set] = data[set].map(fn) | |
| }) | |
| return data | |
| } | |
| function unParsePerson (person) { | |
| if (typeof person === "string") return person | |
| var name = person.name || "" | |
| var u = person.url || person.web | |
| var url = u ? (" ("+u+")") : "" | |
| var e = person.email || person.mail | |
| var email = e ? (" <"+e+">") : "" | |
| return name+email+url | |
| } | |
| function parsePerson (person) { | |
| if (typeof person !== "string") return person | |
| var name = person.match(/^([^\(<]+)/) | |
| var url = person.match(/\(([^\)]+)\)/) | |
| var email = person.match(/<([^>]+)>/) | |
| var 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 addOptionalDepsToDeps (data, warn) { | |
| var o = data.optionalDependencies | |
| if (!o) return; | |
| var d = data.dependencies || {} | |
| Object.keys(o).forEach(function (k) { | |
| d[k] = o[k] | |
| }) | |
| data.dependencies = d | |
| } | |
| function depObjectify (deps, type, warn) { | |
| if (!deps) return {} | |
| if (typeof deps === "string") { | |
| deps = deps.trim().split(/[\n\r\s\t ,]+/) | |
| } | |
| if (!Array.isArray(deps)) return deps | |
| warn("deprecatedArrayDependencies", type) | |
| var o = {} | |
| deps.filter(function (d) { | |
| return typeof d === "string" | |
| }).forEach(function(d) { | |
| d = d.trim().split(/(:?[@\s><=])/) | |
| var dn = d.shift() | |
| var dv = d.join("") | |
| dv = dv.trim() | |
| dv = dv.replace(/^@/, "") | |
| o[dn] = dv | |
| }) | |
| return o | |
| } | |
| function objectifyDeps (data, warn) { | |
| depTypes.forEach(function (type) { | |
| if (!data[type]) return; | |
| data[type] = depObjectify(data[type], type, warn) | |
| }) | |
| } | |
| function bugsTypos(bugs, warn) { | |
| if (!bugs) return | |
| Object.keys(bugs).forEach(function (k) { | |
| if (typos.bugs[k]) { | |
| warn("typo", k, typos.bugs[k], "bugs") | |
| bugs[typos.bugs[k]] = bugs[k] | |
| delete bugs[k] | |
| } | |
| }) | |
| } |