From 1a11eb51f1e21b94ddebc1bee76f7f75af963819 Mon Sep 17 00:00:00 2001 From: Gar Date: Tue, 14 Jan 2025 15:37:02 -0800 Subject: [PATCH] fix: inline normalize-package-data logic This was accomplished by pulling in the code as-is, then adding full test coverage. Only then was dead and unused code pruned. After that some refactoring was done to further clean up the code. --- lib/legacy.js | 255 +++++++++++++++ lib/normalize.js | 27 +- tap-snapshots/test/legacy.js.test.cjs | 235 ++++++++++++++ test/index.js | 2 +- test/legacy.js | 427 ++++++++++++++++++++++++++ 5 files changed, 921 insertions(+), 25 deletions(-) create mode 100644 lib/legacy.js create mode 100644 tap-snapshots/test/legacy.js.test.cjs create mode 100644 test/legacy.js diff --git a/lib/legacy.js b/lib/legacy.js new file mode 100644 index 0000000..bec4ad9 --- /dev/null +++ b/lib/legacy.js @@ -0,0 +1,255 @@ +// Originally normalize-package-data + +const url = require('node:url') +const hostedGitInfo = require('hosted-git-info') +const validateLicense = require('validate-npm-package-license') + +const typos = { + dependancies: 'dependencies', + dependecies: 'dependencies', + depdenencies: 'dependencies', + devEependencies: 'devDependencies', + depends: 'dependencies', + 'dev-dependencies': 'devDependencies', + devDependences: 'devDependencies', + devDepenencies: 'devDependencies', + devdependencies: 'devDependencies', + repostitory: 'repository', + repo: 'repository', + prefereGlobal: 'preferGlobal', + hompage: 'homepage', + hampage: 'homepage', + autohr: 'author', + autor: 'author', + contributers: 'contributors', + publicationConfig: 'publishConfig', + script: 'scripts', +} + +const isEmail = str => str.includes('@') && (str.indexOf('@') < str.lastIndexOf('.')) + +// Extracts description from contents of a readme file in markdown format +function extractDescription (description) { + // the first block of text before the first heading that isn't the first line heading + const lines = description.trim().split('\n') + let start = 0 + // skip initial empty lines and lines that start with # + while (lines[start]?.trim().match(/^(#|$)/)) { + start++ + } + let end = start + 1 + // keep going till we get to the end or an empty line + while (end < lines.length && lines[end].trim()) { + end++ + } + return lines.slice(start, end).join(' ').trim() +} + +function stringifyPerson (person) { + if (typeof person !== 'string') { + const name = person.name || '' + const u = person.url || person.web + const wrappedUrl = u ? (' (' + u + ')') : '' + const e = person.email || person.mail + const wrappedEmail = e ? (' <' + e + '>') : '' + person = name + wrappedEmail + wrappedUrl + } + const matchedName = person.match(/^([^(<]+)/) + const matchedUrl = person.match(/\(([^()]+)\)/) + const matchedEmail = person.match(/<([^<>]+)>/) + const parsed = {} + if (matchedName?.[0].trim()) { + parsed.name = matchedName[0].trim() + } + if (matchedEmail) { + parsed.email = matchedEmail[1] + } + if (matchedUrl) { + parsed.url = matchedUrl[1] + } + return parsed +} + +module.exports = function legacy (data, changes) { + // fixDescriptionField + if (data.description && typeof data.description !== 'string') { + changes?.push(`'description' field should be a string`) + delete data.description + } + if (data.readme && !data.description && data.readme !== 'ERROR: No README data found!') { + data.description = extractDescription(data.readme) + } + if (data.description === undefined) { + delete data.description + } + if (!data.description) { + changes?.push('No description') + } + + // fixModulesField + if (data.modules) { + changes?.push(`modules field is deprecated`) + delete data.modules + } + + // fixFilesField + const files = data.files + if (files && !Array.isArray(files)) { + changes?.push(`Invalid 'files' member`) + delete data.files + } else if (data.files) { + data.files = data.files.filter(function (file) { + if (!file || typeof file !== 'string') { + changes?.push(`Invalid filename in 'files' list: ${file}`) + return false + } else { + return true + } + }) + } + + // fixManField + if (data.man && typeof data.man === 'string') { + data.man = [data.man] + } + + // fixBugsField + if (!data.bugs && data.repository?.url) { + const hosted = hostedGitInfo.fromUrl(data.repository.url) + if (hosted && hosted.bugs()) { + data.bugs = { url: hosted.bugs() } + } + } else if (data.bugs) { + if (typeof data.bugs === 'string') { + if (isEmail(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 { + changes?.push(`Bug string field must be url, email, or {email,url}`) + } + } else { + for (const k in data.bugs) { + if (['web', 'name'].includes(k)) { + changes?.push(`bugs['${k}'] should probably be bugs['url'].`) + data.bugs.url = data.bugs[k] + delete data.bugs[k] + } + } + const 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 { + changes?.push('bugs.url field must be a string url. Deleted.') + } + } + if (oldBugs.email) { + if (typeof (oldBugs.email) === 'string' && isEmail(oldBugs.email)) { + data.bugs.email = oldBugs.email + } else { + changes?.push('bugs.email field must be a string email. Deleted.') + } + } + } + if (!data.bugs.email && !data.bugs.url) { + delete data.bugs + changes?.push('Normalized value of bugs field is an empty object. Deleted.') + } + } + // fixKeywordsField + if (typeof data.keywords === 'string') { + data.keywords = data.keywords.split(/,\s+/) + } + if (data.keywords && !Array.isArray(data.keywords)) { + delete data.keywords + changes?.push(`keywords should be an array of strings`) + } else if (data.keywords) { + data.keywords = data.keywords.filter(function (kw) { + if (typeof kw !== 'string' || !kw) { + changes?.push(`keywords should be an array of strings`) + return false + } else { + return true + } + }) + } + // fixBundleDependenciesField + const bdd = 'bundledDependencies' + const bd = 'bundleDependencies' + if (data[bdd] && !data[bd]) { + data[bd] = data[bdd] + delete data[bdd] + } + if (data[bd] && !Array.isArray(data[bd])) { + changes?.push(`Invalid 'bundleDependencies' list. Must be array of package names`) + delete data[bd] + } else if (data[bd]) { + data[bd] = data[bd].filter(function (filtered) { + if (!filtered || typeof filtered !== 'string') { + changes?.push(`Invalid bundleDependencies member: ${filtered}`) + return false + } else { + if (!data.dependencies) { + data.dependencies = {} + } + if (!Object.prototype.hasOwnProperty.call(data.dependencies, filtered)) { + changes?.push(`Non-dependency in bundleDependencies: ${filtered}`) + data.dependencies[filtered] = '*' + } + return true + } + }) + } + // fixHomepageField + if (!data.homepage && data.repository && data.repository.url) { + const hosted = hostedGitInfo.fromUrl(data.repository.url) + if (hosted) { + data.homepage = hosted.docs() + } + } + if (data.homepage) { + if (typeof data.homepage !== 'string') { + changes?.push('homepage field must be a string url. Deleted.') + delete data.homepage + } else { + /* eslint-disable-next-line node/no-deprecated-api */ + if (!url.parse(data.homepage).protocol) { + data.homepage = 'http://' + data.homepage + } + } + } + // fixReadmeField + if (!data.readme) { + changes?.push('No README data') + data.readme = 'ERROR: No README data found!' + } + // fixLicenseField + const license = data.license || data.licence + if (!license) { + changes?.push('No license field.') + } else if (typeof (license) !== 'string' || license.length < 1 || license.trim() === '') { + changes?.push('license should be a valid SPDX license expression') + } else if (!validateLicense(license).validForNewPackages) { + changes?.push('license should be a valid SPDX license expression') + } + // fixPeople + if (data.author) { + data.author = stringifyPerson(data.author) + } + ['maintainers', 'contributors'].forEach(function (set) { + if (!Array.isArray(data[set])) { + return + } + data[set] = data[set].map(stringifyPerson) + }) + // fixTypos + for (const d in typos) { + if (Object.prototype.hasOwnProperty.call(data, d)) { + changes?.push(`${d} should probably be ${typos[d]}.`) + } + } +} diff --git a/lib/normalize.js b/lib/normalize.js index 3adec01..b70c9fe 100644 --- a/lib/normalize.js +++ b/lib/normalize.js @@ -348,7 +348,6 @@ const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase }) changes?.push(`"readmeFilename" was set to ${readmeFile}`) } if (!data.readme) { - // this.warn('missingReadme') data.readme = 'ERROR: No README data found!' } } @@ -572,30 +571,10 @@ const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase }) } } + // TODO some of this is duplicated in other steps here, a future breaking change may be able to remove the duplicates involved in this step if (steps.includes('normalizeData')) { - const legacyFixer = require('normalize-package-data/lib/fixer.js') - const legacyMakeWarning = require('normalize-package-data/lib/make_warning.js') - legacyFixer.warn = function () { - changes?.push(legacyMakeWarning.apply(null, arguments)) - } - - const legacySteps = [ - 'fixDescriptionField', - 'fixModulesField', - 'fixFilesField', - 'fixManField', - 'fixBugsField', - 'fixKeywordsField', - 'fixBundleDependenciesField', - 'fixHomepageField', - 'fixReadmeField', - 'fixLicenseField', - 'fixPeople', - 'fixTypos', - ] - for (const legacyStep of legacySteps) { - legacyFixer[legacyStep](data) - } + const legacyFixer = require('./legacy.js') + legacyFixer(data, changes) } // Warn if the bin references don't point to anything. This might be better diff --git a/tap-snapshots/test/legacy.js.test.cjs b/tap-snapshots/test/legacy.js.test.cjs new file mode 100644 index 0000000..e6e97e8 --- /dev/null +++ b/tap-snapshots/test/legacy.js.test.cjs @@ -0,0 +1,235 @@ +/* IMPORTANT + * This snapshot file is auto-generated, but designed for humans. + * It should be checked into source control and tracked carefully. + * Re-generate by setting TAP_SNAPSHOT=1 and running tests. + * Make sure to inspect the output below. Do not ignore changes! + */ +'use strict' +exports[`test/legacy.js TAP fixBugsField bugsTypos > must match snapshot 1`] = ` +Array [ + "bugs['web'] should probably be bugs['url'].", +] +` + +exports[`test/legacy.js TAP fixBugsField no bugs with repository with url > must match snapshot 1`] = ` +Array [] +` + +exports[`test/legacy.js TAP fixBugsField non string > must match snapshot 1`] = ` +Array [ + "Normalized value of bugs field is an empty object. Deleted.", +] +` + +exports[`test/legacy.js TAP fixBugsField object email > must match snapshot 1`] = ` +Array [] +` + +exports[`test/legacy.js TAP fixBugsField object invalid url other > must match snapshot 1`] = ` +Array [ + "bugs.url field must be a string url. Deleted.", + "Normalized value of bugs field is an empty object. Deleted.", +] +` + +exports[`test/legacy.js TAP fixBugsField object invalid url string > must match snapshot 1`] = ` +Array [ + "bugs.url field must be a string url. Deleted.", + "Normalized value of bugs field is an empty object. Deleted.", +] +` + +exports[`test/legacy.js TAP fixBugsField object non email > must match snapshot 1`] = ` +Array [ + "bugs.email field must be a string email. Deleted.", + "Normalized value of bugs field is an empty object. Deleted.", +] +` + +exports[`test/legacy.js TAP fixBugsField object non-string email > must match snapshot 1`] = ` +Array [ + "bugs.email field must be a string email. Deleted.", + "Normalized value of bugs field is an empty object. Deleted.", +] +` + +exports[`test/legacy.js TAP fixBugsField object valid url > must match snapshot 1`] = ` +Array [] +` + +exports[`test/legacy.js TAP fixBugsField repository w/ no bugs template > must match snapshot 1`] = ` +Array [] +` + +exports[`test/legacy.js TAP fixBugsField string email > must match snapshot 1`] = ` +Array [] +` + +exports[`test/legacy.js TAP fixBugsField string other > must match snapshot 1`] = ` +Array [ + "Bug string field must be url, email, or {email,url}", + "Normalized value of bugs field is an empty object. Deleted.", +] +` + +exports[`test/legacy.js TAP fixBugsField string url > must match snapshot 1`] = ` +Array [] +` + +exports[`test/legacy.js TAP fixBundleDependenciesField bundledDependencies > must match snapshot 1`] = ` +Array [] +` + +exports[`test/legacy.js TAP fixBundleDependenciesField filters non strings > must match snapshot 1`] = ` +Array [ + "Invalid bundleDependencies member: 100", +] +` + +exports[`test/legacy.js TAP fixBundleDependenciesField non array > must match snapshot 1`] = ` +Array [ + "Invalid 'bundleDependencies' list. Must be array of package names", +] +` + +exports[`test/legacy.js TAP fixBundleDependenciesField non-dependency > must match snapshot 1`] = ` +Array [ + "Non-dependency in bundleDependencies: @npm/test", +] +` + +exports[`test/legacy.js TAP fixDescriptionField no description and no readme > must match snapshot 1`] = ` +Array [ + "No description", + "No README data", +] +` + +exports[`test/legacy.js TAP fixDescriptionField non string > must match snapshot 1`] = ` +Array [ + "'description' field should be a string", +] +` + +exports[`test/legacy.js TAP fixDescriptionField summarizes readme > must match snapshot 1`] = ` +Array [] +` + +exports[`test/legacy.js TAP fixFilesField invalid entry > must match snapshot 1`] = ` +Array [ + "Invalid filename in 'files' list: null", + "Invalid filename in 'files' list: true", +] +` + +exports[`test/legacy.js TAP fixFilesField non array > must match snapshot 1`] = ` +Array [ + "Invalid 'files' member", +] +` + +exports[`test/legacy.js TAP fixHomepageField no homepage with repository with url > must match snapshot 1`] = ` +Array [] +` + +exports[`test/legacy.js TAP fixHomepageField no protocol > must match snapshot 1`] = ` +Array [] +` + +exports[`test/legacy.js TAP fixHomepageField non hosted repository > must match snapshot 1`] = ` +Array [] +` + +exports[`test/legacy.js TAP fixHomepageField non string > must match snapshot 1`] = ` +Array [ + "homepage field must be a string url. Deleted.", +] +` + +exports[`test/legacy.js TAP fixHomepageField repository w/ no docs template > must match snapshot 1`] = ` +Array [] +` + +exports[`test/legacy.js TAP fixKeywordsField filters non strings > must match snapshot 1`] = ` +Array [ + "keywords should be an array of strings", +] +` + +exports[`test/legacy.js TAP fixKeywordsField non array > must match snapshot 1`] = ` +Array [ + "keywords should be an array of strings", +] +` + +exports[`test/legacy.js TAP fixKeywordsField splits string > must match snapshot 1`] = ` +Array [] +` + +exports[`test/legacy.js TAP fixLicenseField invalid > must match snapshot 1`] = ` +Array [ + "license should be a valid SPDX license expression", +] +` + +exports[`test/legacy.js TAP fixLicenseField missing > must match snapshot 1`] = ` +Array [ + "No license field.", +] +` + +exports[`test/legacy.js TAP fixLicenseField non string > must match snapshot 1`] = ` +Array [ + "license should be a valid SPDX license expression", +] +` + +exports[`test/legacy.js TAP fixManfield string > must match snapshot 1`] = ` +Array [] +` + +exports[`test/legacy.js TAP fixModulesField > must match snapshot 1`] = ` +Array [ + "modules field is deprecated", +] +` + +exports[`test/legacy.js TAP fixPeople author name url and email > must match snapshot 1`] = ` +Array [] +` + +exports[`test/legacy.js TAP fixPeople author no name > must match snapshot 1`] = ` +Array [] +` + +exports[`test/legacy.js TAP fixPeople author only name > must match snapshot 1`] = ` +Array [] +` + +exports[`test/legacy.js TAP fixPeople author string > must match snapshot 1`] = ` +Array [] +` + +exports[`test/legacy.js TAP fixPeople author web and mail > must match snapshot 1`] = ` +Array [] +` + +exports[`test/legacy.js TAP fixPeople contributors > must match snapshot 1`] = ` +Array [] +` + +exports[`test/legacy.js TAP fixPeople maintainers > must match snapshot 1`] = ` +Array [] +` + +exports[`test/legacy.js TAP fixReadmeField no readme > must match snapshot 1`] = ` +Array [ + "No README data", +] +` + +exports[`test/legacy.js TAP fixTypos top level > must match snapshot 1`] = ` +Array [ + "script should probably be scripts.", +] +` diff --git a/test/index.js b/test/index.js index baf0564..4c2b774 100644 --- a/test/index.js +++ b/test/index.js @@ -1,7 +1,7 @@ const fs = require('node:fs') const { join, resolve } = require('node:path') const t = require('tap') -const PackageJson = require('../lib/index.js') +const PackageJson = require('../') const getPackageFile = (file) => JSON.parse( diff --git a/test/legacy.js b/test/legacy.js new file mode 100644 index 0000000..dc0a9be --- /dev/null +++ b/test/legacy.js @@ -0,0 +1,427 @@ +const t = require('tap') +const PackageJson = require('../') + +const base = { + name: '@npmcli/test', + description: 'test fixture', + version: '0.0.0', + readme: 'test fixture package', + license: 'UNLICENSED', +} + +function legacy (t, data) { + const changes = [] + const p = new PackageJson().fromContent(data) + p.normalize({ steps: 'normalizeData', changes }) + t.matchSnapshot(changes) + return p +} + +t.test('fixDescriptionField', async t => { + t.test('non string', async t => { + const { content } = legacy(t, { + ...base, + description: true, + }) + t.equal(content.description, base.readme) + }) + + t.test('no description and no readme', async t => { + const { content } = legacy(t, { + ...base, + description: undefined, + readme: undefined, + }) + t.equal(content.description, undefined) + }) + + t.test('summarizes readme', async t => { + const { content } = legacy(t, { + ...base, + description: undefined, + readme: '# test package\ntest fixture readme\nsecond line\n\nthird line', + }) + t.equal(content.description, 'test fixture readme second line') + }) +}) + +t.test('fixModulesField', async t => { + const { content } = legacy(t, { + ...base, + modules: true, + }) + t.equal(content.modules, undefined) +}) + +t.test('fixFilesField', async t => { + t.test('non array', async t => { + const { content } = legacy(t, { + ...base, + files: './index.js', + }) + t.equal(content.files, undefined) + }) + + t.test('invalid entry', async t => { + const { content } = legacy(t, { + ...base, + files: [null, true, './index.js'], + }) + t.same(content.files, ['./index.js']) + }) +}) + +t.test('fixManfield', async t => { + t.test('string', async t => { + const { content } = legacy(t, { + ...base, + man: './man', + }) + t.same(content.man, ['./man']) + }) +}) + +t.test('fixBugsField', async t => { + t.test('no bugs with repository with url', async t => { + const { content } = legacy(t, { + ...base, + repository: { + url: 'git+https://github.com/npm/package-json.git', + }, + }) + t.same(content.bugs, { url: 'https://github.com/npm/package-json/issues' }) + }) + + t.test('non string', async t => { + const { content } = legacy(t, { + ...base, + bugs: {}, + }) + t.same(content.bugs, undefined) + }) + + t.test('string email', async t => { + const { content } = legacy(t, { + ...base, + bugs: 'support@npmjs.org', + }) + t.same(content.bugs, { email: 'support@npmjs.org' }) + }) + + t.test('string url', async t => { + const { content } = legacy(t, { + ...base, + bugs: 'https://npmjs.org', + }) + t.same(content.bugs, { url: 'https://npmjs.org' }) + }) + + t.test('string other', async t => { + const { content } = legacy(t, { + ...base, + bugs: 'something else', + }) + t.equal(content.bugs, undefined) + }) + + t.test('bugsTypos', async t => { + const { content } = legacy(t, { + ...base, + bugs: { web: 'https://npmjs.org' }, + }) + t.same(content.bugs, { url: 'https://npmjs.org' }) + }) + + t.test('object valid url', async t => { + const { content } = legacy(t, { + ...base, + bugs: { url: 'https://npmjs.org' }, + }) + t.same(content.bugs, { url: 'https://npmjs.org' }) + }) + + t.test('object invalid url string', async t => { + const { content } = legacy(t, { + ...base, + bugs: { url: 'homepage' }, + }) + t.equal(content.bugs, undefined) + }) + + t.test('object invalid url other', async t => { + const { content } = legacy(t, { + ...base, + bugs: { url: {} }, + }) + t.equal(content.bugs, undefined) + }) + + t.test('object email', async t => { + const { content } = legacy(t, { + ...base, + bugs: { email: 'support@npmjs.org' }, + }) + t.same(content.bugs, { email: 'support@npmjs.org' }) + }) + + t.test('object non email', async t => { + const { content } = legacy(t, { + ...base, + bugs: { email: 'support' }, + }) + t.equal(content.bugs, undefined) + }) + + t.test('object non-string email', async t => { + const { content } = legacy(t, { + ...base, + bugs: { email: {} }, + }) + t.equal(content.bugs, undefined) + }) + + t.test('repository w/ no bugs template', async t => { + const { content } = legacy(t, { + ...base, + repository: { url: 'https://git.sr.ht/example/repo.git' }, + }) + t.equal(content.bugs, undefined) + }) +}) + +t.test('fixKeywordsField', async t => { + t.test('splits string', async t => { + const { content } = legacy(t, { + ...base, + keywords: 'a, b, c', + }) + t.same(content.keywords, ['a', 'b', 'c']) + }) + + t.test('non array', async t => { + const { content } = legacy(t, { + ...base, + keywords: {}, + }) + t.equal(content.keywords, undefined) + }) + + t.test('filters non strings', async t => { + const { content } = legacy(t, { + ...base, + keywords: ['a', 100, 'c'], + }) + t.same(content.keywords, ['a', 'c']) + }) +}) + +t.test('fixBundleDependenciesField', async t => { + t.test('bundledDependencies', async t => { + const { content } = legacy(t, { + ...base, + dependencies: { '@npm/test': '*' }, + bundledDependencies: ['@npm/test'], + }) + t.equal(content.bundledDependencies, undefined) + t.same(content.bundleDependencies, ['@npm/test']) + }) + + t.test('non array', async t => { + const { content } = legacy(t, { + ...base, + bundleDependencies: '@npm/test', + }) + t.equal(content.bundleDependencies, undefined) + }) + + t.test('filters non strings', async t => { + const { content } = legacy(t, { + ...base, + dependencies: { '@npm/test': '*' }, + bundleDependencies: ['@npm/test', 100], + }) + t.same(content.bundleDependencies, ['@npm/test']) + }) + + t.test('non-dependency', async t => { + const { content } = legacy(t, { + ...base, + bundleDependencies: ['@npm/test'], + }) + t.same(content.bundleDependencies, ['@npm/test']) + }) +}) + +t.test('fixHomepageField', async t => { + t.test('no homepage with repository with url', async t => { + const { content } = legacy(t, { + ...base, + repository: { + url: 'git+https://github.com/npm/package-json.git', + }, + }) + t.equal(content.homepage, 'https://github.com/npm/package-json#readme') + }) + + t.test('non string', async t => { + const { content } = legacy(t, { + ...base, + homepage: true, + }) + t.equal(content.homepage, undefined) + }) + + t.test('no protocol', async t => { + const { content } = legacy(t, { + ...base, + homepage: 'npmjs.org', + }) + t.equal(content.homepage, 'http://npmjs.org') + }) + + t.test('repository w/ no docs template', async t => { + const { content } = legacy(t, { + ...base, + repository: { url: 'https://git.sr.ht/example/repo.git' }, + }) + t.equal(content.docs, undefined) + }) + + t.test('non hosted repository', async t => { + const { content } = legacy(t, { + ...base, + repository: { url: 'https://npmjs.org' }, + }) + t.equal(content.docs, undefined) + }) +}) + +t.test('fixReadmeField', async t => { + t.test('no readme', async t => { + const { content } = legacy(t, { + ...base, + readme: undefined, + }) + t.equal(content.readme, 'ERROR: No README data found!') + }) +}) + +t.test('fixLicenseField', async t => { + t.test('missing', async t => { + const { content } = legacy(t, { + ...base, + license: undefined, + }) + t.equal(content.license, undefined) + }) + + t.test('non string', async t => { + const { content } = legacy(t, { + ...base, + license: 100, + }) + t.equal(content.license, 100) + }) + + t.test('invalid', async t => { + const { content } = legacy(t, { + ...base, + license: 'BESPOKE LICENSE', + }) + t.equal(content.license, 'BESPOKE LICENSE') + }) +}) + +t.test('fixPeople', async t => { + t.test('author', async t => { + t.test('string', async t => { + const { content } = legacy(t, { + ...base, + author: 'npm', + }) + t.same(content.author, { name: 'npm' }) + }) + + t.test('no name', async t => { + const { content } = legacy(t, { + ...base, + author: { + url: 'https://npmjs.org', + }, + }) + t.same(content.author, { + url: 'https://npmjs.org', + }) + }) + + t.test('name url and email', async t => { + const { content } = legacy(t, { + ...base, + author: { + name: 'npm', + url: 'https://npmjs.org', + email: 'support@npmjs.org', + }, + }) + t.same(content.author, { + name: 'npm', + url: 'https://npmjs.org', + email: 'support@npmjs.org', + }) + }) + + t.test('web and mail', async t => { + const { content } = legacy(t, { + ...base, + author: { + name: 'npm', + web: 'https://npmjs.org', + mail: 'support@npmjs.org', + }, + }) + t.same(content.author, { + name: 'npm', + url: 'https://npmjs.org', + email: 'support@npmjs.org', + }) + }) + + t.test('only name', async t => { + const { content } = legacy(t, { + ...base, + author: { + name: 'npm', + }, + }) + t.same(content.author, { name: 'npm' }) + }) + }) + + t.test('maintainers', async t => { + const { content } = legacy(t, { + ...base, + maintainers: ['npm'], + }) + t.same(content.maintainers, [{ name: 'npm' }]) + }) + + t.test('contributors', async t => { + const { content } = legacy(t, { + ...base, + contributors: ['npm'], + }) + t.same(content.contributors, [{ name: 'npm' }]) + }) +}) + +t.test('fixTypos', async t => { + t.test('top level', async t => { + const { content } = legacy(t, { + ...base, + script: { + lint: 'npm run lint', + }, + }) + t.same(content.script, { lint: 'npm run lint' }) + }) +})