Socket
Socket
Sign inDemoInstall

@npmcli/package-json

Package Overview
Dependencies
Maintainers
6
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@npmcli/package-json - npm Package Compare versions

Comparing version 4.0.0 to 4.0.1

2

lib/index.js

@@ -45,5 +45,3 @@ const { readFile, writeFile } = require('fs/promises')

'fixRepositoryField',
'fixBinField',
'fixDependencies',
'fixScriptsField',
'devDependencies',

@@ -50,0 +48,0 @@ 'scriptpath',

236

lib/normalize.js

@@ -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 @@ },

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc