@npmcli/package-json
Advanced tools
Comparing version 4.0.0 to 4.0.1
@@ -45,5 +45,3 @@ const { readFile, writeFile } = require('fs/promises') | ||
'fixRepositoryField', | ||
'fixBinField', | ||
'fixDependencies', | ||
'fixScriptsField', | ||
'devDependencies', | ||
@@ -50,0 +48,0 @@ 'scriptpath', |
@@ -0,4 +1,4 @@ | ||
const semver = require('semver') | ||
const fs = require('fs/promises') | ||
const { glob } = require('glob') | ||
const normalizePackageBin = require('npm-normalize-package-bin') | ||
const legacyFixer = require('normalize-package-data/lib/fixer.js') | ||
@@ -9,3 +9,81 @@ const legacyMakeWarning = require('normalize-package-data/lib/make_warning.js') | ||
const git = require('@npmcli/git') | ||
const hostedGitInfo = require('hosted-git-info') | ||
// used to be npm-normalize-package-bin | ||
function normalizePackageBin (pkg, changes) { | ||
if (pkg.bin) { | ||
if (typeof pkg.bin === 'string' && pkg.name) { | ||
changes?.push('"bin" was converted to an object') | ||
pkg.bin = { [pkg.name]: pkg.bin } | ||
} else if (Array.isArray(pkg.bin)) { | ||
changes?.push('"bin" was converted to an object') | ||
pkg.bin = pkg.bin.reduce((acc, k) => { | ||
acc[path.basename(k)] = k | ||
return acc | ||
}, {}) | ||
} | ||
if (typeof pkg.bin === 'object') { | ||
for (const binKey in pkg.bin) { | ||
if (typeof pkg.bin[binKey] !== 'string') { | ||
delete pkg.bin[binKey] | ||
changes?.push(`removed invalid "bin[${binKey}]"`) | ||
continue | ||
} | ||
const base = path.join('/', path.basename(binKey.replace(/\\|:/g, '/'))).slice(1) | ||
if (!base) { | ||
delete pkg.bin[binKey] | ||
changes?.push(`removed invalid "bin[${binKey}]"`) | ||
continue | ||
} | ||
const binTarget = path.join('/', pkg.bin[binKey].replace(/\\/g, '/')) | ||
.replace(/\\/g, '/').slice(1) | ||
if (!binTarget) { | ||
delete pkg.bin[binKey] | ||
changes?.push(`removed invalid "bin[${binKey}]"`) | ||
continue | ||
} | ||
if (base !== binKey) { | ||
delete pkg.bin[binKey] | ||
changes?.push(`"bin[${binKey}]" was renamed to "bin[${base}]"`) | ||
} | ||
if (binTarget !== pkg.bin[binKey]) { | ||
changes?.push(`"bin[${base}]" script name was cleaned`) | ||
} | ||
pkg.bin[base] = binTarget | ||
} | ||
if (Object.keys(pkg.bin).length === 0) { | ||
changes?.push('empty "bin" was removed') | ||
delete pkg.bin | ||
} | ||
return pkg | ||
} | ||
} | ||
delete pkg.bin | ||
} | ||
function isCorrectlyEncodedName (spec) { | ||
return !spec.match(/[/@\s+%:]/) && | ||
spec === encodeURIComponent(spec) | ||
} | ||
function isValidScopedPackageName (spec) { | ||
if (spec.charAt(0) !== '@') { | ||
return false | ||
} | ||
const 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]) | ||
} | ||
// We don't want the `changes` array in here by default because this is a hot | ||
@@ -22,13 +100,45 @@ // path for parsing packuments during install. So the calling method passes it | ||
legacyFixer.warn = function () { | ||
changes?.push(legacyMakeWarning.apply(null, arguments)) | ||
} | ||
// name and version are load bearing so we have to clean them up first | ||
if (steps.includes('fixNameField') || steps.includes('normalizeData')) { | ||
legacyFixer.fixNameField(data, { strict, allowLegacyCase }) | ||
if (!data.name && !strict) { | ||
changes?.push('Missing "name" field was set to an empty string') | ||
data.name = '' | ||
} else { | ||
if (typeof data.name !== 'string') { | ||
throw new Error('name field must be a string.') | ||
} | ||
if (!strict) { | ||
const name = data.name.trim() | ||
if (data.name !== name) { | ||
changes?.push(`Whitespace was trimmed from "name"`) | ||
data.name = name | ||
} | ||
} | ||
if (data.name.startsWith('.') || | ||
!(isValidScopedPackageName(data.name) || isCorrectlyEncodedName(data.name)) || | ||
(strict && (!allowLegacyCase) && data.name !== data.name.toLowerCase()) || | ||
data.name.toLowerCase() === 'node_modules' || | ||
data.name.toLowerCase() === 'favicon.ico') { | ||
throw new Error('Invalid name: ' + JSON.stringify(data.name)) | ||
} | ||
} | ||
} | ||
if (steps.includes('fixVersionField') || steps.includes('normalizeData')) { | ||
legacyFixer.fixVersionField(data, strict) | ||
// allow "loose" semver 1.0 versions in non-strict mode | ||
// enforce strict semver 2.0 compliance in strict mode | ||
const loose = !strict | ||
if (!data.version) { | ||
data.version = '' | ||
} else { | ||
if (!semver.valid(data.version, loose)) { | ||
throw new Error(`Invalid version: "${data.version}"`) | ||
} | ||
const version = semver.clean(data.version, loose) | ||
if (version !== data.version) { | ||
changes?.push(`"version" was cleaned and set to "${version}"`) | ||
data.version = version | ||
} | ||
} | ||
} | ||
@@ -54,2 +164,3 @@ // remove attributes that start with "_" | ||
// fix bundledDependencies typo | ||
// normalize bundleDependencies | ||
if (steps.includes('bundledDependencies')) { | ||
@@ -76,3 +187,3 @@ if (data.bundleDependencies === undefined && data.bundledDependencies !== undefined) { | ||
} | ||
} else { | ||
} else if ('bundleDependencies' in data) { | ||
changes?.push(`"bundleDependencies" was removed`) | ||
@@ -91,7 +202,7 @@ delete data.bundleDependencies | ||
for (const name in data.optionalDependencies) { | ||
changes?.push(`optionalDependencies entry "${name}" was removed`) | ||
changes?.push(`optionalDependencies."${name}" was removed`) | ||
delete data.dependencies[name] | ||
} | ||
if (!Object.keys(data.dependencies).length) { | ||
changes?.push(`empty "optionalDependencies" was removed`) | ||
changes?.push(`Empty "optionalDependencies" was removed`) | ||
delete data.dependencies | ||
@@ -129,2 +240,3 @@ } | ||
// strip "node_modules/.bin" from scripts entries | ||
// remove invalid scripts entries (non-strings) | ||
if (steps.includes('scripts') || steps.includes('scriptpath')) { | ||
@@ -136,4 +248,4 @@ const spre = /^(\.[/\\])?node_modules[/\\].bin[\\/]/ | ||
delete data.scripts[name] | ||
changes?.push(`invalid scripts entry "${name}" was removed`) | ||
} else if (steps.includes('scriptpath')) { | ||
changes?.push(`Invalid scripts."${name}" was removed`) | ||
} else if (steps.includes('scriptpath') && spre.test(data.scripts[name])) { | ||
data.scripts[name] = data.scripts[name].replace(spre, '') | ||
@@ -144,3 +256,3 @@ changes?.push(`scripts entry "${name}" was fixed to remove node_modules/.bin reference`) | ||
} else { | ||
changes?.push(`removed invalid "scripts"`) | ||
changes?.push(`Removed invalid "scripts"`) | ||
delete data.scripts | ||
@@ -165,3 +277,3 @@ } | ||
data.contributors = authors | ||
changes.push('"contributors" was auto-populated with the contents of the "AUTHORS" file') | ||
changes?.push('"contributors" was auto-populated with the contents of the "AUTHORS" file') | ||
} catch { | ||
@@ -213,3 +325,3 @@ // do nothing | ||
if (steps.includes('bin') || steps.includes('binDir') || steps.includes('binRefs')) { | ||
normalizePackageBin(data) | ||
normalizePackageBin(data, changes) | ||
} | ||
@@ -229,3 +341,3 @@ | ||
// *sigh* | ||
normalizePackageBin(data) | ||
normalizePackageBin(data, changes) | ||
} | ||
@@ -334,18 +446,92 @@ | ||
if (steps.includes('fixRepositoryField') || steps.includes('normalizeData')) { | ||
legacyFixer.fixRepositoryField(data) | ||
if (data.repositories) { | ||
/* eslint-disable-next-line max-len */ | ||
changes?.push(`"repository" was set to the first entry in "repositories" (${data.repository})`) | ||
data.repository = data.repositories[0] | ||
} | ||
if (data.repository) { | ||
if (typeof data.repository === 'string') { | ||
changes?.push('"repository" was changed from a string to an object') | ||
data.repository = { | ||
type: 'git', | ||
url: data.repository, | ||
} | ||
} | ||
if (data.repository.url) { | ||
const hosted = hostedGitInfo.fromUrl(data.repository.url) | ||
let r | ||
if (hosted) { | ||
if (hosted.getDefaultRepresentation() === 'shortcut') { | ||
r = hosted.https() | ||
} else { | ||
r = hosted.toString() | ||
} | ||
if (r !== data.repository.url) { | ||
changes?.push(`"repository.url" was normalized to "${r}"`) | ||
data.repository.url = r | ||
} | ||
} | ||
} | ||
} | ||
} | ||
if (steps.includes('fixBinField') || steps.includes('normalizeData')) { | ||
legacyFixer.fixBinField(data) | ||
} | ||
if (steps.includes('fixDependencies') || steps.includes('normalizeData')) { | ||
legacyFixer.fixDependencies(data, strict) | ||
} | ||
// peerDependencies? | ||
// devDependencies is meaningless here, it's ignored on an installed package | ||
for (const type of ['dependencies', 'devDependencies', 'optionalDependencies']) { | ||
if (data[type]) { | ||
let secondWarning = true | ||
if (typeof data[type] === 'string') { | ||
changes?.push(`"${type}" was converted from a string into an object`) | ||
data[type] = data[type].trim().split(/[\n\r\s\t ,]+/) | ||
secondWarning = false | ||
} | ||
if (Array.isArray(data[type])) { | ||
if (secondWarning) { | ||
changes?.push(`"${type}" was converted from an array into an object`) | ||
} | ||
const o = {} | ||
for (const d of data[type]) { | ||
if (typeof d === 'string') { | ||
const dep = d.trim().split(/(:?[@\s><=])/) | ||
const dn = dep.shift() | ||
const dv = dep.join('').replace(/^@/, '').trim() | ||
o[dn] = dv | ||
} | ||
} | ||
data[type] = o | ||
} | ||
} | ||
} | ||
// normalize-package-data used to put optional dependencies BACK into | ||
// dependencies here, we no longer do this | ||
if (steps.includes('fixScriptsField') || steps.includes('normalizeData')) { | ||
legacyFixer.fixScriptsField(data) | ||
for (const deps of ['dependencies', 'devDependencies']) { | ||
if (deps in data) { | ||
if (!data[deps] || typeof data[deps] !== 'object') { | ||
changes?.push(`Removed invalid "${deps}"`) | ||
delete data[deps] | ||
} else { | ||
for (const d in data[deps]) { | ||
const r = data[deps][d] | ||
if (typeof r !== 'string') { | ||
changes?.push(`Removed invalid "${deps}.${d}"`) | ||
delete data[deps][d] | ||
} | ||
const hosted = hostedGitInfo.fromUrl(data[deps][d])?.toString() | ||
if (hosted && hosted !== data[deps][d]) { | ||
changes?.push(`Normalized git reference to "${deps}.${d}"`) | ||
data[deps][d] = hosted.toString() | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
if (steps.includes('normalizeData')) { | ||
legacyFixer.warn = function () { | ||
changes?.push(legacyMakeWarning.apply(null, arguments)) | ||
} | ||
const legacySteps = [ | ||
@@ -352,0 +538,0 @@ 'fixDescriptionField', |
{ | ||
"name": "@npmcli/package-json", | ||
"version": "4.0.0", | ||
"version": "4.0.1", | ||
"description": "Programmatic API to update package.json", | ||
@@ -28,3 +28,3 @@ "main": "lib/index.js", | ||
"@npmcli/eslint-config": "^4.0.0", | ||
"@npmcli/template-oss": "4.15.1", | ||
"@npmcli/template-oss": "4.17.0", | ||
"read-package-json": "^6.0.4", | ||
@@ -37,6 +37,7 @@ "read-package-json-fast": "^3.0.2", | ||
"glob": "^10.2.2", | ||
"hosted-git-info": "^6.1.1", | ||
"json-parse-even-better-errors": "^3.0.0", | ||
"normalize-package-data": "^5.0.0", | ||
"npm-normalize-package-bin": "^3.0.1", | ||
"proc-log": "^3.0.0" | ||
"proc-log": "^3.0.0", | ||
"semver": "^7.5.3" | ||
}, | ||
@@ -52,3 +53,3 @@ "repository": { | ||
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", | ||
"version": "4.15.1", | ||
"version": "4.17.0", | ||
"publish": "true" | ||
@@ -55,0 +56,0 @@ }, |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
36924
870
7
+ Addedhosted-git-info@^6.1.1
+ Addedsemver@^7.5.3
- Removednpm-normalize-package-bin@^3.0.1