You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
474 lines
12 KiB
474 lines
12 KiB
var isValidSemver = require('semver/functions/valid') |
|
var cleanSemver = require('semver/functions/clean') |
|
var validateLicense = require('validate-npm-package-license') |
|
var hostedGitInfo = require('hosted-git-info') |
|
var isBuiltinModule = require('is-core-module') |
|
var depTypes = ['dependencies', 'devDependencies', 'optionalDependencies'] |
|
var extractDescription = require('./extract_description') |
|
var url = require('url') |
|
var typos = require('./typos.json') |
|
|
|
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 (Object.prototype.hasOwnProperty.call(data, 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 (Object.prototype.hasOwnProperty.call(data.dependencies, bd)) { |
|
this.warn('nonDependencyBundleDependency', bd) |
|
data.dependencies[bd] = '*' |
|
} |
|
return true |
|
} |
|
}, this) |
|
} |
|
}, |
|
|
|
fixDependencies: function (data, 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 (!isValidSemver(data.version, loose)) { |
|
throw new Error('Invalid version: "' + data.version + '"') |
|
} |
|
data.version = cleanSemver(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} |
|
/* eslint-disable-next-line node/no-deprecated-api */ |
|
} 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) { |
|
/* eslint-disable-next-line node/no-deprecated-api */ |
|
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 |
|
} |
|
/* eslint-disable-next-line node/no-deprecated-api */ |
|
if (!url.parse(data.homepage).protocol) { |
|
data.homepage = 'http://' + data.homepage |
|
} |
|
}, |
|
|
|
fixLicenseField: function (data) { |
|
const license = data.license || data.licence |
|
if (!license) { |
|
return this.warn('missingLicense') |
|
} |
|
if ( |
|
typeof (license) !== 'string' || |
|
license.length < 1 || |
|
license.trim() === '' |
|
) { |
|
return this.warn('invalidLicense') |
|
} |
|
if (!validateLicense(license).validForNewPackages) { |
|
return 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] |
|
} |
|
}) |
|
}
|
|
|