From 19214b54828a9b4e923ea329fa5841edf0753162 Mon Sep 17 00:00:00 2001 From: Gar Date: Tue, 16 May 2023 09:51:24 -0700 Subject: [PATCH] deps: @npmcli/package-json@3.1.0 --- DEPENDENCIES.md | 9 +- .../@npmcli/package-json/lib/index.js | 157 ++++++++-- .../@npmcli/package-json/lib/normalize.js | 284 ++++++++++++++++++ .../@npmcli/package-json/package.json | 14 +- package-lock.json | 13 +- package.json | 2 +- 6 files changed, 439 insertions(+), 40 deletions(-) create mode 100644 node_modules/@npmcli/package-json/lib/normalize.js diff --git a/DEPENDENCIES.md b/DEPENDENCIES.md index 7c9e187f4783f..a7df0ec604f95 100644 --- a/DEPENDENCIES.md +++ b/DEPENDENCIES.md @@ -214,6 +214,8 @@ graph LR; npmcli-mock-registry-->npmcli-template-oss["@npmcli/template-oss"]; npmcli-mock-registry-->pacote; npmcli-package-json-->json-parse-even-better-errors; + npmcli-package-json-->normalize-package-data; + npmcli-package-json-->npm-normalize-package-bin; npmcli-run-script-->npmcli-node-gyp["@npmcli/node-gyp"]; npmcli-run-script-->npmcli-promise-spawn["@npmcli/promise-spawn"]; npmcli-run-script-->read-package-json-fast; @@ -694,7 +696,10 @@ graph LR; npmcli-mock-registry-->tap; npmcli-move-file-->mkdirp; npmcli-move-file-->rimraf; + npmcli-package-json-->glob; npmcli-package-json-->json-parse-even-better-errors; + npmcli-package-json-->normalize-package-data; + npmcli-package-json-->npm-normalize-package-bin; npmcli-promise-spawn-->which; npmcli-query-->postcss-selector-parser; npmcli-run-script-->node-gyp; @@ -817,6 +822,6 @@ packages higher up the chain. - pacote, libnpmhook, libnpmorg, libnpmsearch, libnpmteam, npm-profile - npm-registry-fetch, libnpmversion - @npmcli/git, make-fetch-happen, @npmcli/config, init-package-json - - @npmcli/installed-package-contents, @npmcli/map-workspaces, cacache, npm-pick-manifest, @npmcli/run-script, read-package-json, promzard - - @npmcli/docs, @npmcli/fs, npm-bundled, read-package-json-fast, unique-filename, npm-install-checks, npm-package-arg, npm-packlist, normalize-package-data, @npmcli/package-json, bin-links, nopt, npmlog, parse-conflict-json, read + - @npmcli/installed-package-contents, @npmcli/map-workspaces, cacache, npm-pick-manifest, @npmcli/run-script, read-package-json, @npmcli/package-json, promzard + - @npmcli/docs, @npmcli/fs, npm-bundled, read-package-json-fast, unique-filename, npm-install-checks, npm-package-arg, npm-packlist, normalize-package-data, bin-links, nopt, npmlog, parse-conflict-json, read - @npmcli/eslint-config, @npmcli/template-oss, ignore-walk, semver, npm-normalize-package-bin, @npmcli/name-from-folder, json-parse-even-better-errors, fs-minipass, ssri, unique-slug, @npmcli/promise-spawn, hosted-git-info, proc-log, validate-npm-package-name, @npmcli/node-gyp, minipass-fetch, @npmcli/query, cmd-shim, read-cmd-shim, write-file-atomic, abbrev, are-we-there-yet, gauge, minify-registry-metadata, ini, @npmcli/disparity-colors, mute-stream, npm-audit-report, npm-user-validate diff --git a/node_modules/@npmcli/package-json/lib/index.js b/node_modules/@npmcli/package-json/lib/index.js index e98308f3d3b84..34e415b45d49f 100644 --- a/node_modules/@npmcli/package-json/lib/index.js +++ b/node_modules/@npmcli/package-json/lib/index.js @@ -1,18 +1,12 @@ -const fs = require('fs') -const promisify = require('util').promisify -const readFile = promisify(fs.readFile) -const writeFile = promisify(fs.writeFile) +const { readFile, writeFile } = require('fs/promises') const { resolve } = require('path') const updateDeps = require('./update-dependencies.js') const updateScripts = require('./update-scripts.js') const updateWorkspaces = require('./update-workspaces.js') +const normalize = require('./normalize.js') const parseJSON = require('json-parse-even-better-errors') -const _filename = Symbol('filename') -const _manifest = Symbol('manifest') -const _readFileContent = Symbol('readFileContent') - // a list of handy specialized helper functions that take // care of special cases that are handled by the npm cli const knownSteps = new Set([ @@ -29,42 +23,111 @@ const knownKeys = new Set([ ]) class PackageJson { + static normalizeSteps = Object.freeze([ + '_id', + '_attributes', + 'bundledDependencies', + 'bundleDependencies', + 'optionalDedupe', + 'scripts', + 'funding', + 'bin', + ]) + + static prepareSteps = Object.freeze([ + '_attributes', + 'bundledDependencies', + 'bundleDependencies', + 'gypfile', + 'serverjs', + 'scriptpath', + 'authors', + 'readme', + 'mans', + 'binDir', + 'gitHead', + 'fillTypes', + 'normalizeData', + 'binRefs', + ]) + + // default behavior, just loads and parses static async load (path) { return await new PackageJson(path).load() } + // read-package-json compatible behavior + static async prepare (path, opts) { + return await new PackageJson(path).prepare(opts) + } + + // read-package-json-fast compatible behavior + static async normalize (path, opts) { + return await new PackageJson(path).normalize(opts) + } + + #filename + #path + #manifest = {} + #readFileContent = '' + #fromIndex = false + constructor (path) { - this[_filename] = resolve(path, 'package.json') - this[_manifest] = {} - this[_readFileContent] = '' + this.#path = path + this.#filename = resolve(path, 'package.json') } - async load () { + async load (parseIndex) { + let parseErr try { - this[_readFileContent] = - await readFile(this[_filename], 'utf8') + this.#readFileContent = + await readFile(this.#filename, 'utf8') } catch (err) { - throw new Error('package.json not found') + err.message = `Could not read package.json: ${err}` + if (!parseIndex) { + throw err + } + parseErr = err + } + + if (parseErr) { + const indexFile = resolve(this.#path, 'index.js') + let indexFileContent + try { + indexFileContent = await readFile(indexFile, 'utf8') + } catch (err) { + throw parseErr + } + try { + this.#manifest = fromComment(indexFileContent) + } catch (err) { + throw parseErr + } + this.#fromIndex = true + return this } try { - this[_manifest] = - parseJSON(this[_readFileContent]) + this.#manifest = parseJSON(this.#readFileContent) } catch (err) { - throw new Error(`Invalid package.json: ${err}`) + err.message = `Invalid package.json: ${err}` + throw err } - return this } get content () { - return this[_manifest] + return this.#manifest + } + + get path () { + return this.#path } update (content) { // validates both current manifest and content param const invalidContent = - typeof this[_manifest] !== 'object' + typeof this.#manifest !== 'object' || typeof content !== 'object' if (invalidContent) { throw Object.assign( @@ -74,13 +137,13 @@ class PackageJson { } for (const step of knownSteps) { - this[_manifest] = step({ content, originalContent: this[_manifest] }) + this.#manifest = step({ content, originalContent: this.#manifest }) } // unknown properties will just be overwitten for (const [key, value] of Object.entries(content)) { if (!knownKeys.has(key)) { - this[_manifest][key] = value + this.#manifest[key] = value } } @@ -88,22 +151,62 @@ class PackageJson { } async save () { + if (this.#fromIndex) { + throw new Error('No package.json to save to') + } const { [Symbol.for('indent')]: indent, [Symbol.for('newline')]: newline, - } = this[_manifest] + } = this.#manifest const format = indent === undefined ? ' ' : indent const eol = newline === undefined ? '\n' : newline const fileContent = `${ - JSON.stringify(this[_manifest], null, format) + JSON.stringify(this.#manifest, null, format) }\n` .replace(/\n/g, eol) - if (fileContent.trim() !== this[_readFileContent].trim()) { - return await writeFile(this[_filename], fileContent) + if (fileContent.trim() !== this.#readFileContent.trim()) { + return await writeFile(this.#filename, fileContent) } } + + async normalize (opts = {}) { + if (!opts.steps) { + opts.steps = this.constructor.normalizeSteps + } + await this.load() + await normalize(this, opts) + return this + } + + async prepare (opts = {}) { + if (!opts.steps) { + opts.steps = this.constructor.prepareSteps + } + 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') + } + 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 diff --git a/node_modules/@npmcli/package-json/lib/normalize.js b/node_modules/@npmcli/package-json/lib/normalize.js new file mode 100644 index 0000000000000..bc101cd4fde1b --- /dev/null +++ b/node_modules/@npmcli/package-json/lib/normalize.js @@ -0,0 +1,284 @@ +const fs = require('fs/promises') +const { glob } = require('glob') +const normalizePackageBin = require('npm-normalize-package-bin') +const normalizePackageData = require('normalize-package-data') +const path = require('path') + +const normalize = async (pkg, { strict, steps }) => { + const data = pkg.content + const scripts = data.scripts || {} + + // remove attributes that start with "_" + if (steps.includes('_attributes')) { + for (const key in data) { + if (key.startsWith('_')) { + delete pkg.content[key] + } + } + } + + // build the "_id" attribute + if (steps.includes('_id')) { + if (data.name && data.version) { + data._id = `${data.name}@${data.version}` + } + } + + // fix bundledDependencies typo + if (steps.includes('bundledDependencies')) { + if (data.bundleDependencies === undefined && data.bundledDependencies !== undefined) { + data.bundleDependencies = data.bundledDependencies + } + delete data.bundledDependencies + } + // expand "bundleDependencies: true or translate from object" + if (steps.includes('bundleDependencies')) { + const bd = data.bundleDependencies + if (bd === true) { + data.bundleDependencies = Object.keys(data.dependencies || {}) + } else if (bd && typeof bd === 'object') { + if (!Array.isArray(bd)) { + data.bundleDependencies = Object.keys(bd) + } + } else { + delete data.bundleDependencies + } + } + + // it was once common practice to list deps both in optionalDependencies and + // in dependencies, to support npm versions that did not know about + // optionalDependencies. This is no longer a relevant need, so duplicating + // the deps in two places is unnecessary and excessive. + if (steps.includes('optionalDedupe')) { + if (data.dependencies && + data.optionalDependencies && typeof data.optionalDependencies === 'object') { + for (const name in data.optionalDependencies) { + delete data.dependencies[name] + } + if (!Object.keys(data.dependencies).length) { + delete data.dependencies + } + } + } + + // add "install" attribute if any "*.gyp" files exist + if (steps.includes('gypfile')) { + if (!scripts.install && !scripts.preinstall && data.gypfile !== false) { + const files = await glob('*.gyp', { cwd: pkg.path }) + if (files.length) { + scripts.install = 'node-gyp rebuild' + data.scripts = scripts + data.gypfile = true + } + } + } + + // add "start" attribute if "server.js" exists + if (steps.includes('serverjs') && !scripts.start) { + try { + await fs.access(path.join(pkg.path, 'server.js')) + scripts.start = 'node server.js' + data.scripts = scripts + } catch { + // do nothing + } + } + + // strip "node_modules/.bin" from scripts entries + if (steps.includes('scripts') || steps.includes('scriptpath')) { + const spre = /^(\.[/\\])?node_modules[/\\].bin[\\/]/ + if (typeof data.scripts === 'object') { + for (const name in data.scripts) { + if (typeof data.scripts[name] !== 'string') { + delete data.scripts[name] + } else if (steps.includes('scriptpath')) { + data.scripts[name] = data.scripts[name].replace(spre, '') + } + } + } else { + delete data.scripts + } + } + + if (steps.includes('funding')) { + if (data.funding && typeof data.funding === 'string') { + data.funding = { url: data.funding } + } + } + + // populate "authors" attribute + if (steps.includes('authors') && !data.contributors) { + try { + const authorData = await fs.readFile(path.join(pkg.path, 'AUTHORS'), 'utf8') + const authors = authorData.split(/\r?\n/g) + .map(line => line.replace(/^\s*#.*$/, '').trim()) + .filter(line => line) + data.contributors = authors + } catch { + // do nothing + } + } + + // populate "readme" attribute + if (steps.includes('readme') && !data.readme) { + const mdre = /\.m?a?r?k?d?o?w?n?$/i + const files = await glob('{README,README.*}', { cwd: pkg.path, nocase: true, mark: true }) + let readmeFile + for (const file of files) { + // don't accept directories. + if (!file.endsWith(path.sep)) { + if (file.match(mdre)) { + readmeFile = file + break + } + if (file.endsWith('README')) { + readmeFile = file + } + } + } + if (readmeFile) { + const readmeData = await fs.readFile(path.join(pkg.path, readmeFile), 'utf8') + data.readme = readmeData + data.readmeFilename = readmeFile + } + } + + // expand directories.man + if (steps.includes('mans') && !data.man && data.directories?.man) { + const manDir = data.directories.man + const cwd = path.resolve(pkg.path, manDir) + const files = await glob('**/*.[0-9]', { cwd }) + data.man = files.map(man => + path.relative(pkg.path, path.join(cwd, man)).split(path.sep).join('/') + ) + } + + if (steps.includes('bin') || steps.includes('binDir') || steps.includes('binRefs')) { + normalizePackageBin(data) + } + + // expand "directories.bin" + if (steps.includes('binDir') && data.directories?.bin) { + const binsDir = path.resolve(pkg.path, path.join('.', path.join('/', data.directories.bin))) + const bins = await glob('**', { cwd: binsDir }) + data.bin = bins.reduce((acc, binFile) => { + if (binFile && !binFile.startsWith('.')) { + const binName = path.basename(binFile) + acc[binName] = path.join(data.directories.bin, binFile) + } + return acc + }, {}) + // *sigh* + normalizePackageBin(data) + } + + // populate "gitHead" attribute + if (steps.includes('gitHead') && !data.gitHead) { + let head + try { + head = await fs.readFile(path.resolve(pkg.path, '.git/HEAD'), 'utf8') + } catch (err) { + // do nothing + } + let headData + if (head) { + if (head.startsWith('ref: ')) { + const headRef = head.replace(/^ref: /, '').trim() + const headFile = path.resolve(pkg.path, '.git', headRef) + try { + headData = await fs.readFile(headFile, 'utf8') + headData = headData.replace(/^ref: /, '').trim() + } catch (err) { + // do nothing + } + if (!headData) { + const packFile = path.resolve(pkg.path, '.git/packed-refs') + try { + let refs = await fs.readFile(packFile, 'utf8') + if (refs) { + refs = refs.split('\n') + for (let i = 0; i < refs.length; i++) { + const match = refs[i].match(/^([0-9a-f]{40}) (.+)$/) + if (match && match[2].trim() === headRef) { + headData = match[1] + break + } + } + } + } catch { + // do nothing + } + } + } else { + headData = head.trim() + } + } + if (headData) { + data.gitHead = headData + } + } + + // populate "types" attribute + if (steps.includes('fillTypes')) { + const index = data.main || 'index.js' + + if (typeof index !== 'string') { + throw new TypeError('The "main" attribute must be of type string.') + } + + // TODO exports is much more complicated than this in verbose format + // We need to support for instance + + // "exports": { + // ".": [ + // { + // "default": "./lib/npm.js" + // }, + // "./lib/npm.js" + // ], + // "./package.json": "./package.json" + // }, + // as well as conditional exports + + // if (data.exports && typeof data.exports === 'string') { + // index = data.exports + // } + + // if (data.exports && data.exports['.']) { + // index = data.exports['.'] + // if (typeof index !== 'string') { + // } + // } + const extless = path.join(path.dirname(index), path.basename(index, path.extname(index))) + const dts = `./${extless}.d.ts` + const hasDTSFields = 'types' in data || 'typings' in data + if (!hasDTSFields) { + try { + await fs.access(path.join(pkg.path, dts)) + data.types = dts.split(path.sep).join('/') + } catch { + // do nothing + } + } + } + + // "normalizeData" from read-package-json + if (steps.includes('normalizeData')) { + normalizePackageData(data, strict) + } + + // Warn if the bin references don't point to anything. This might be better + // in normalize-package-data if it had access to the file path. + if (steps.includes('binRefs') && data.bin instanceof Object) { + for (const key in data.bin) { + const binPath = path.resolve(pkg.path, data.bin[key]) + try { + await fs.access(binPath) + } catch { + delete data.bin[key] + } + } + } +} + +module.exports = normalize diff --git a/node_modules/@npmcli/package-json/package.json b/node_modules/@npmcli/package-json/package.json index faae7891a1e72..61607c5bb6ae7 100644 --- a/node_modules/@npmcli/package-json/package.json +++ b/node_modules/@npmcli/package-json/package.json @@ -1,6 +1,6 @@ { "name": "@npmcli/package-json", - "version": "3.0.0", + "version": "3.1.0", "description": "Programmatic API to update package.json", "main": "lib/index.js", "files": [ @@ -24,12 +24,15 @@ "author": "GitHub Inc.", "license": "ISC", "devDependencies": { - "@npmcli/eslint-config": "^3.0.1", - "@npmcli/template-oss": "4.5.1", + "@npmcli/eslint-config": "^4.0.0", + "@npmcli/template-oss": "4.15.1", "tap": "^16.0.1" }, "dependencies": { - "json-parse-even-better-errors": "^3.0.0" + "glob": "^10.2.2", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^5.0.0", + "npm-normalize-package-bin": "^3.0.1" }, "repository": { "type": "git", @@ -40,7 +43,8 @@ }, "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", - "version": "4.5.1" + "version": "4.15.1", + "publish": "true" }, "tap": { "nyc-arg": [ diff --git a/package-lock.json b/package-lock.json index 692a5871b7678..b0dab2437e66d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -87,7 +87,7 @@ "@npmcli/arborist": "^6.2.9", "@npmcli/config": "^6.1.6", "@npmcli/map-workspaces": "^3.0.4", - "@npmcli/package-json": "^3.0.0", + "@npmcli/package-json": "^3.1.0", "@npmcli/run-script": "^6.0.2", "abbrev": "^2.0.0", "archy": "~1.0.0", @@ -2311,12 +2311,15 @@ } }, "node_modules/@npmcli/package-json": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-3.0.0.tgz", - "integrity": "sha512-NnuPuM97xfiCpbTEJYtEuKz6CFbpUHtaT0+5via5pQeI25omvQDFbp1GcGJ/c4zvL/WX0qbde6YiLgfZbWFgvg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-3.1.0.tgz", + "integrity": "sha512-qNPy6Yf9ruFST99xcrl5EWAvrb7qFrwgVbwdzcTJlIgxbArKOq5e/bgZ6rTL1X9hDgAdPbvL8RWx/OTLSB0ToA==", "inBundle": true, "dependencies": { - "json-parse-even-better-errors": "^3.0.0" + "glob": "^10.2.2", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^5.0.0", + "npm-normalize-package-bin": "^3.0.1" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" diff --git a/package.json b/package.json index 452184aba643a..5faef77f95891 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "@npmcli/arborist": "^6.2.9", "@npmcli/config": "^6.1.6", "@npmcli/map-workspaces": "^3.0.4", - "@npmcli/package-json": "^3.0.0", + "@npmcli/package-json": "^3.1.0", "@npmcli/run-script": "^6.0.2", "abbrev": "^2.0.0", "archy": "~1.0.0",