419 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			419 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
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]
 | 
						|
    }
 | 
						|
  })
 | 
						|
}
 |