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 3.1.1 to 4.0.0

172

lib/index.js

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

// npm pkg fix
static fixSteps = Object.freeze([
'binRefs',
'bundleDependencies',
'bundleDependenciesFalse',
'fixNameField',
'fixVersionField',
'fixRepositoryField',
'fixBinField',
'fixDependencies',
'fixScriptsField',
'devDependencies',
'scriptpath',
])
static prepareSteps = Object.freeze([
'_id',
'_attributes',

@@ -56,10 +72,43 @@ 'bundledDependencies',

// default behavior, just loads and parses
static async load (path) {
return await new PackageJson(path).load()
// create a new empty package.json, so we can save at the given path even
// though we didn't start from a parsed file
static async create (path, opts = {}) {
const p = new PackageJson()
await p.create(path)
if (opts.data) {
return p.update(opts.data)
}
return p
}
// Loads a package.json at given path and JSON parses
static async load (path, opts = {}) {
const p = new PackageJson()
// Avoid try/catch if we aren't going to create
if (!opts.create) {
return p.load(path)
}
try {
return await p.load(path)
} catch (err) {
if (!err.message.startsWith('Could not read package.json')) {
throw err
}
return await p.create(path)
}
}
// npm pkg fix
static async fix (path, opts) {
const p = new PackageJson()
await p.load(path, true)
return p.fix(opts)
}
// read-package-json compatible behavior
static async prepare (path, opts) {
return await new PackageJson(path).prepare(opts)
const p = new PackageJson()
await p.load(path, true)
return p.prepare(opts)
}

@@ -69,21 +118,18 @@

static async normalize (path, opts) {
return await new PackageJson(path).normalize(opts)
const p = new PackageJson()
await p.load(path)
return p.normalize(opts)
}
#filename
#path
#manifest = {}
#manifest
#readFileContent = ''
#fromIndex = false
#canSave = true
constructor (path) {
// Load content from given path
async load (path, parseIndex) {
this.#path = path
this.#filename = resolve(path, 'package.json')
}
async load (parseIndex) {
let parseErr
try {
this.#readFileContent =
await readFile(this.#filename, 'utf8')
this.#readFileContent = await readFile(this.filename, 'utf8')
} catch (err) {

@@ -98,3 +144,3 @@ err.message = `Could not read package.json: ${err}`

if (parseErr) {
const indexFile = resolve(this.#path, 'index.js')
const indexFile = resolve(this.path, 'index.js')
let indexFileContent

@@ -107,12 +153,18 @@ try {

try {
this.#manifest = fromComment(indexFileContent)
this.fromComment(indexFileContent)
} catch (err) {
throw parseErr
}
this.#fromIndex = true
// This wasn't a package.json so prevent saving
this.#canSave = false
return this
}
return this.fromJSON(this.#readFileContent)
}
// Load data from a JSON string/buffer
fromJSON (data) {
try {
this.#manifest = parseJSON(this.#readFileContent)
this.#manifest = parseJSON(data)
} catch (err) {

@@ -125,2 +177,23 @@ err.message = `Invalid package.json: ${err}`

// Load data from a comment
// /**package { "name": "foo", "version": "1.2.3", ... } **/
fromComment (data) {
data = data.split(/^\/\*\*package(?:\s|$)/m)
if (data.length < 2) {
throw new Error('File has no package in comments')
}
data = data[1]
data = data.split(/\*\*\/$/m)
if (data.length < 2) {
throw new Error('File has no package in comments')
}
data = data[0]
data = data.replace(/^\s*\*/mg, '')
this.#manifest = parseJSON(data)
return this
}
get content () {

@@ -134,16 +207,23 @@ return this.#manifest

get filename () {
if (this.path) {
return resolve(this.path, 'package.json')
}
return undefined
}
create (path) {
this.#path = path
this.#manifest = {}
return this
}
// This should be the ONLY way to set content in the manifest
update (content) {
// validates both current manifest and content param
const invalidContent =
typeof this.#manifest !== 'object'
|| typeof content !== 'object'
if (invalidContent) {
throw Object.assign(
new Error(`Can't update invalid package.json data`),
{ code: 'EPACKAGEJSONUPDATE' }
)
if (!this.content) {
throw new Error('Can not update without content. Please `load` or `create`')
}
for (const step of knownSteps) {
this.#manifest = step({ content, originalContent: this.#manifest })
this.#manifest = step({ content, originalContent: this.content })
}

@@ -154,3 +234,3 @@

if (!knownKeys.has(key)) {
this.#manifest[key] = value
this.content[key] = value
}

@@ -163,3 +243,3 @@ }

async save () {
if (this.#fromIndex) {
if (!this.#canSave) {
throw new Error('No package.json to save to')

@@ -170,3 +250,3 @@ }

[Symbol.for('newline')]: newline,
} = this.#manifest
} = this.content

@@ -176,3 +256,3 @@ const format = indent === undefined ? ' ' : indent

const fileContent = `${
JSON.stringify(this.#manifest, null, format)
JSON.stringify(this.content, null, format)
}\n`

@@ -182,3 +262,3 @@ .replace(/\n/g, eol)

if (fileContent.trim() !== this.#readFileContent.trim()) {
return await writeFile(this.#filename, fileContent)
return await writeFile(this.filename, fileContent)
}

@@ -191,3 +271,2 @@ }

}
await this.load()
await normalize(this, opts)

@@ -201,27 +280,14 @@ return this

}
await this.load(true)
await normalize(this, opts)
return this
}
}
// /**package { "name": "foo", "version": "1.2.3", ... } **/
function fromComment (data) {
data = data.split(/^\/\*\*package(?:\s|$)/m)
if (data.length < 2) {
throw new Error('File has no package in comments')
async fix (opts = {}) {
// This one is not overridable
opts.steps = this.constructor.fixSteps
await normalize(this, opts)
return this
}
data = data[1]
data = data.split(/\*\*\/$/m)
if (data.length < 2) {
throw new Error('File has no package in comments')
}
data = data[0]
data = data.replace(/^\s*\*/mg, '')
return parseJSON(data)
}
module.exports = PackageJson
const fs = require('fs/promises')
const { glob } = require('glob')
const normalizePackageBin = require('npm-normalize-package-bin')
const normalizePackageData = require('normalize-package-data')
const legacyFixer = require('normalize-package-data/lib/fixer.js')
const legacyMakeWarning = require('normalize-package-data/lib/make_warning.js')
const path = require('path')

@@ -9,3 +10,9 @@ const log = require('proc-log')

const normalize = async (pkg, { strict, steps, root }) => {
// We don't want the `changes` array in here by default because this is a hot
// path for parsing packuments during install. So the calling method passes it
// in if it wants to track changes.
const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase }) => {
if (!pkg.content) {
throw new Error('Can not normalize without content')
}
const data = pkg.content

@@ -15,2 +22,14 @@ const scripts = data.scripts || {}

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 (steps.includes('fixVersionField') || steps.includes('normalizeData')) {
legacyFixer.fixVersionField(data, strict)
}
// remove attributes that start with "_"

@@ -20,2 +39,3 @@ if (steps.includes('_attributes')) {

if (key.startsWith('_')) {
changes?.push(`"${key}" was removed`)
delete pkg.content[key]

@@ -29,2 +49,3 @@ }

if (data.name && data.version) {
changes?.push(`"_id" was set to ${pkgId}`)
data._id = pkgId

@@ -39,2 +60,3 @@ }

}
changes?.push(`Deleted incorrect "bundledDependencies"`)
delete data.bundledDependencies

@@ -46,10 +68,14 @@ }

if (bd === false && !steps.includes('bundleDependenciesDeleteFalse')) {
changes?.push(`"bundleDependencies" was changed from "false" to "[]"`)
data.bundleDependencies = []
} else if (bd === true) {
changes?.push(`"bundleDependencies" was auto-populated from "dependencies"`)
data.bundleDependencies = Object.keys(data.dependencies || {})
} else if (bd && typeof bd === 'object') {
if (!Array.isArray(bd)) {
changes?.push(`"bundleDependencies" was changed from an object to an array`)
data.bundleDependencies = Object.keys(bd)
}
} else {
changes?.push(`"bundleDependencies" was removed`)
delete data.bundleDependencies

@@ -67,5 +93,7 @@ }

for (const name in data.optionalDependencies) {
changes?.push(`optionalDependencies entry "${name}" was removed`)
delete data.dependencies[name]
}
if (!Object.keys(data.dependencies).length) {
changes?.push(`empty "optionalDependencies" was removed`)
delete data.dependencies

@@ -84,2 +112,4 @@ }

data.gypfile = true
changes?.push(`"scripts.install" was set to "node-gyp rebuild"`)
changes?.push(`"gypfile" was set to "true"`)
}

@@ -95,2 +125,3 @@ }

data.scripts = scripts
changes?.push('"scripts.start" was set to "node server.js"')
} catch {

@@ -108,7 +139,10 @@ // do nothing

delete data.scripts[name]
changes?.push(`invalid scripts entry "${name}" was removed`)
} else if (steps.includes('scriptpath')) {
data.scripts[name] = data.scripts[name].replace(spre, '')
changes?.push(`scripts entry "${name}" was fixed to remove node_modules/.bin reference`)
}
}
} else {
changes?.push(`removed invalid "scripts"`)
delete data.scripts

@@ -121,2 +155,3 @@ }

data.funding = { url: data.funding }
changes?.push(`"funding" was changed to an object with a url attribute`)
}

@@ -133,2 +168,3 @@ }

data.contributors = authors
changes.push('"contributors" was auto-populated with the contents of the "AUTHORS" file')
} catch {

@@ -160,3 +196,9 @@ // do nothing

data.readmeFilename = readmeFile
changes?.push(`"readme" was set to the contents of ${readmeFile}`)
changes?.push(`"readmeFilename" was set to ${readmeFile}`)
}
if (!data.readme) {
// this.warn('missingReadme')
data.readme = 'ERROR: No README data found!'
}
}

@@ -286,5 +328,43 @@

// "normalizeData" from read-package-json
// "normalizeData" from "read-package-json", which was just a call through to
// "normalize-package-data". We only call the "fixer" functions because
// outside of that it was also clobbering _id (which we already conditionally
// do) and also adding the gypfile script (which we also already
// conditionally do)
// Some steps are isolated so we can do a limited subset of these in `fix`
if (steps.includes('fixRepositoryField') || steps.includes('normalizeData')) {
legacyFixer.fixRepositoryField(data)
}
if (steps.includes('fixBinField') || steps.includes('normalizeData')) {
legacyFixer.fixBinField(data)
}
if (steps.includes('fixDependencies') || steps.includes('normalizeData')) {
legacyFixer.fixDependencies(data, strict)
}
if (steps.includes('fixScriptsField') || steps.includes('normalizeData')) {
legacyFixer.fixScriptsField(data)
}
if (steps.includes('normalizeData')) {
normalizePackageData(data, strict)
const legacySteps = [
'fixDescriptionField',
'fixModulesField',
'fixFilesField',
'fixManField',
'fixBugsField',
'fixKeywordsField',
'fixBundleDependenciesField',
'fixHomepageField',
'fixReadmeField',
'fixLicenseField',
'fixPeople',
'fixTypos',
]
for (const legacyStep of legacySteps) {
legacyFixer[legacyStep](data)
}
}

@@ -291,0 +371,0 @@

{
"name": "@npmcli/package-json",
"version": "3.1.1",
"version": "4.0.0",
"description": "Programmatic API to update package.json",

@@ -5,0 +5,0 @@ "main": "lib/index.js",

@@ -55,28 +55,33 @@ # @npmcli/package-json

### `constructor(path)`
### `constructor()`
Creates a new instance of `PackageJson`.
Creates a new empty instance of `PackageJson`.
- `path`: `String` that points to the folder from where to read the
`package.json` from
---
### `async PackageJson.create(path)`
Creates an empty `package.json` at the given path. If one already exists
it will be overwritten.
---
### `async PackageJson.load()`
### `async PackageJson.load(path, opts = {})`
Loads the `package.json` at location determined in the `path` option of
the constructor.
Loads a `package.json` at the given path.
- `opts`: `Object` can contain:
- `create`: `Boolean` if true, a new package.json will be created if one does not already exist. Will not clobber ane existing package.json that can not be parsed.
### Example:
Loads contents of the `package.json` file located at `./`:
Loads contents of a `package.json` file located at `./`:
```js
const PackageJson = require('@npmcli/package-json')
const pkgJson = new PackageJson('./')
await pkgJson.load()
const pkgJson = new PackageJson()
await pkgJson.load('./')
```
Throws an error in case the `package.json` file is missing or has invalid
contents.
Throws an error in case a `package.json` file is missing or has invalid contents.

@@ -87,11 +92,9 @@ ---

Convenience static method that returns a new instance and loads the contents of
the `package.json` file from that location.
Convenience static method that returns a new instance and loads the contents of a `package.json` file from that location.
- `path`: `String` that points to the folder from where to read the
`package.json` from
- `path`: `String` that points to the folder from where to read the `package.json` from
### Example:
Loads contents of the `package.json` file located at `./`:
Loads contents of a `package.json` file located at `./`:

@@ -107,13 +110,23 @@ ```js

Like `load` but intended for reading package.json files in a
node_modules tree. Some light normalization is done to ensure that it
is ready for use in `@npmcli/arborist`
Intended for normalizing package.json files in a node_modules tree. Some light normalization is done to ensure that it is ready for use in `@npmcli/arborist`
- `path`: `String` that points to the folder from where to read the `package.json` from
- `opts`: `Object` can contain:
- `strict`: `Boolean` enables optional strict mode when applying the `normalizeData` step
- `steps`: `Array` optional normalization steps that will be applied to the `package.json` file, replacing the default steps
- `root`: `Path` optional git root to provide when applying the `gitHead` step
- `changes`: `Array` if provided, a message about each change that was made to the packument will be added to this array
---
### **static** `async PackageJson.normalize(path)`
### **static** `async PackageJson.normalize(path, opts = {})`
Convenience static method like `load` but for calling `normalize`
Convenience static that calls `load` before calling `normalize`
---
- `path`: `String` that points to the folder from where to read the `package.json` from
- `opts`: `Object` can contain:
- `strict`: `Boolean` enables optional strict mode when applying the `normalizeData` step
- `steps`: `Array` optional normalization steps that will be applied to the `package.json` file, replacing the default steps
- `root`: `Path` optional git root to provide when applying the `gitHead` step
- `changes`: `Array` if provided, a message about each change that was made to the packument will be added to this array

@@ -124,15 +137,28 @@ ---

Like `load` but intended for reading package.json files before publish.
Like `normalize` but intended for preparing package.json files for publish.
---
### **static** `async PackageJson.prepare(path)`
### **static** `async PackageJson.prepare(path, opts = {})`
Convenience static method like `load` but for calling `prepare`
Convenience static that calls `load` before calling `prepare`
- `path`: `String` that points to the folder from where to read the `package.json` from
- `opts`: `Object` can contain:
- `strict`: `Boolean` enables optional strict mode when applying the `normalizeData` step
- `steps`: `Array` optional normalization steps that will be applied to the `package.json` file, replacing the default steps
- `root`: `Path` optional git root to provide when applying the `gitHead` step
- `changes`: `Array` if provided, a message about each change that was made to the packument will be added to this array
---
### `async PackageJson.fix()`
Like `normalize` but intended for the `npm pkg fix` command.
---
### `PackageJson.update(content)`
Updates the contents of the `package.json` with the `content` provided.
Updates the contents of a `package.json` with the `content` provided.

@@ -144,3 +170,3 @@ - `content`: `Object` containing the properties to be updated/replaced in the

`optionalDependencies`, `peerDependencies` will have special logic to handle
the update of these options, such as deduplications.
the update of these options, such as sorting and deduplication.

@@ -208,19 +234,5 @@ ### Example:

Saves the current `content` to the same location used when initializing
this instance.
Saves the current `content` to the same location used when calling
`load()`.
<br />
## Related
When you make a living out of reading and writing `package.json` files, you end
up with quite the amount of packages dedicated to it, the **npm cli** also
uses:
- [read-package-json-fast](https://github.com/npm/read-package-json-fast) reads
and normalizes `package.json` files the way the **npm cli** expects it.
- [read-package-json](https://github.com/npm/read-package-json) reads and
normalizes more info from your `package.json` file. Used by `npm@6` and in
`npm@7` for publishing.
## LICENSE

@@ -227,0 +239,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