Skip to content

Commit a0763d3

Browse files
committed
deps: @npmcli/package-json@4.0.1
1 parent 3829998 commit a0763d3

File tree

6 files changed

+229
-41
lines changed

6 files changed

+229
-41
lines changed

DEPENDENCIES.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -216,11 +216,12 @@ graph LR;
216216
npmcli-mock-registry-->npmcli-eslint-config["@npmcli/eslint-config"];
217217
npmcli-mock-registry-->npmcli-template-oss["@npmcli/template-oss"];
218218
npmcli-mock-registry-->pacote;
219+
npmcli-package-json-->hosted-git-info;
219220
npmcli-package-json-->json-parse-even-better-errors;
220221
npmcli-package-json-->normalize-package-data;
221-
npmcli-package-json-->npm-normalize-package-bin;
222222
npmcli-package-json-->npmcli-git["@npmcli/git"];
223223
npmcli-package-json-->proc-log;
224+
npmcli-package-json-->semver;
224225
npmcli-run-script-->npmcli-node-gyp["@npmcli/node-gyp"];
225226
npmcli-run-script-->npmcli-promise-spawn["@npmcli/promise-spawn"];
226227
npmcli-run-script-->read-package-json-fast;
@@ -700,11 +701,12 @@ graph LR;
700701
npmcli-mock-registry-->pacote;
701702
npmcli-mock-registry-->tap;
702703
npmcli-package-json-->glob;
704+
npmcli-package-json-->hosted-git-info;
703705
npmcli-package-json-->json-parse-even-better-errors;
704706
npmcli-package-json-->normalize-package-data;
705-
npmcli-package-json-->npm-normalize-package-bin;
706707
npmcli-package-json-->npmcli-git["@npmcli/git"];
707708
npmcli-package-json-->proc-log;
709+
npmcli-package-json-->semver;
708710
npmcli-promise-spawn-->which;
709711
npmcli-query-->postcss-selector-parser;
710712
npmcli-run-script-->node-gyp;

node_modules/@npmcli/package-json/lib/index.js

-2
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,7 @@ class PackageJson {
4242
'fixNameField',
4343
'fixVersionField',
4444
'fixRepositoryField',
45-
'fixBinField',
4645
'fixDependencies',
47-
'fixScriptsField',
4846
'devDependencies',
4947
'scriptpath',
5048
])

node_modules/@npmcli/package-json/lib/normalize.js

+211-25
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,89 @@
1+
const semver = require('semver')
12
const fs = require('fs/promises')
23
const { glob } = require('glob')
3-
const normalizePackageBin = require('npm-normalize-package-bin')
44
const legacyFixer = require('normalize-package-data/lib/fixer.js')
55
const legacyMakeWarning = require('normalize-package-data/lib/make_warning.js')
66
const path = require('path')
77
const log = require('proc-log')
88
const git = require('@npmcli/git')
9+
const hostedGitInfo = require('hosted-git-info')
10+
11+
// used to be npm-normalize-package-bin
12+
function normalizePackageBin (pkg, changes) {
13+
if (pkg.bin) {
14+
if (typeof pkg.bin === 'string' && pkg.name) {
15+
changes?.push('"bin" was converted to an object')
16+
pkg.bin = { [pkg.name]: pkg.bin }
17+
} else if (Array.isArray(pkg.bin)) {
18+
changes?.push('"bin" was converted to an object')
19+
pkg.bin = pkg.bin.reduce((acc, k) => {
20+
acc[path.basename(k)] = k
21+
return acc
22+
}, {})
23+
}
24+
if (typeof pkg.bin === 'object') {
25+
for (const binKey in pkg.bin) {
26+
if (typeof pkg.bin[binKey] !== 'string') {
27+
delete pkg.bin[binKey]
28+
changes?.push(`removed invalid "bin[${binKey}]"`)
29+
continue
30+
}
31+
const base = path.join('/', path.basename(binKey.replace(/\\|:/g, '/'))).slice(1)
32+
if (!base) {
33+
delete pkg.bin[binKey]
34+
changes?.push(`removed invalid "bin[${binKey}]"`)
35+
continue
36+
}
37+
38+
const binTarget = path.join('/', pkg.bin[binKey].replace(/\\/g, '/'))
39+
.replace(/\\/g, '/').slice(1)
40+
41+
if (!binTarget) {
42+
delete pkg.bin[binKey]
43+
changes?.push(`removed invalid "bin[${binKey}]"`)
44+
continue
45+
}
46+
47+
if (base !== binKey) {
48+
delete pkg.bin[binKey]
49+
changes?.push(`"bin[${binKey}]" was renamed to "bin[${base}]"`)
50+
}
51+
if (binTarget !== pkg.bin[binKey]) {
52+
changes?.push(`"bin[${base}]" script name was cleaned`)
53+
}
54+
pkg.bin[base] = binTarget
55+
}
56+
57+
if (Object.keys(pkg.bin).length === 0) {
58+
changes?.push('empty "bin" was removed')
59+
delete pkg.bin
60+
}
61+
62+
return pkg
63+
}
64+
}
65+
delete pkg.bin
66+
}
67+
68+
function isCorrectlyEncodedName (spec) {
69+
return !spec.match(/[/@\s+%:]/) &&
70+
spec === encodeURIComponent(spec)
71+
}
72+
73+
function isValidScopedPackageName (spec) {
74+
if (spec.charAt(0) !== '@') {
75+
return false
76+
}
77+
78+
const rest = spec.slice(1).split('/')
79+
if (rest.length !== 2) {
80+
return false
81+
}
82+
83+
return rest[0] && rest[1] &&
84+
rest[0] === encodeURIComponent(rest[0]) &&
85+
rest[1] === encodeURIComponent(rest[1])
86+
}
987

1088
// We don't want the `changes` array in here by default because this is a hot
1189
// path for parsing packuments during install. So the calling method passes it
@@ -18,17 +96,49 @@ const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase })
1896
const scripts = data.scripts || {}
1997
const pkgId = `${data.name ?? ''}@${data.version ?? ''}`
2098

21-
legacyFixer.warn = function () {
22-
changes?.push(legacyMakeWarning.apply(null, arguments))
23-
}
24-
2599
// name and version are load bearing so we have to clean them up first
26100
if (steps.includes('fixNameField') || steps.includes('normalizeData')) {
27-
legacyFixer.fixNameField(data, { strict, allowLegacyCase })
101+
if (!data.name && !strict) {
102+
changes?.push('Missing "name" field was set to an empty string')
103+
data.name = ''
104+
} else {
105+
if (typeof data.name !== 'string') {
106+
throw new Error('name field must be a string.')
107+
}
108+
if (!strict) {
109+
const name = data.name.trim()
110+
if (data.name !== name) {
111+
changes?.push(`Whitespace was trimmed from "name"`)
112+
data.name = name
113+
}
114+
}
115+
116+
if (data.name.startsWith('.') ||
117+
!(isValidScopedPackageName(data.name) || isCorrectlyEncodedName(data.name)) ||
118+
(strict && (!allowLegacyCase) && data.name !== data.name.toLowerCase()) ||
119+
data.name.toLowerCase() === 'node_modules' ||
120+
data.name.toLowerCase() === 'favicon.ico') {
121+
throw new Error('Invalid name: ' + JSON.stringify(data.name))
122+
}
123+
}
28124
}
29125

30126
if (steps.includes('fixVersionField') || steps.includes('normalizeData')) {
31-
legacyFixer.fixVersionField(data, strict)
127+
// allow "loose" semver 1.0 versions in non-strict mode
128+
// enforce strict semver 2.0 compliance in strict mode
129+
const loose = !strict
130+
if (!data.version) {
131+
data.version = ''
132+
} else {
133+
if (!semver.valid(data.version, loose)) {
134+
throw new Error(`Invalid version: "${data.version}"`)
135+
}
136+
const version = semver.clean(data.version, loose)
137+
if (version !== data.version) {
138+
changes?.push(`"version" was cleaned and set to "${version}"`)
139+
data.version = version
140+
}
141+
}
32142
}
33143
// remove attributes that start with "_"
34144
if (steps.includes('_attributes')) {
@@ -49,6 +159,7 @@ const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase })
49159
}
50160

51161
// fix bundledDependencies typo
162+
// normalize bundleDependencies
52163
if (steps.includes('bundledDependencies')) {
53164
if (data.bundleDependencies === undefined && data.bundledDependencies !== undefined) {
54165
data.bundleDependencies = data.bundledDependencies
@@ -70,7 +181,7 @@ const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase })
70181
changes?.push(`"bundleDependencies" was changed from an object to an array`)
71182
data.bundleDependencies = Object.keys(bd)
72183
}
73-
} else {
184+
} else if ('bundleDependencies' in data) {
74185
changes?.push(`"bundleDependencies" was removed`)
75186
delete data.bundleDependencies
76187
}
@@ -84,11 +195,11 @@ const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase })
84195
if (data.dependencies &&
85196
data.optionalDependencies && typeof data.optionalDependencies === 'object') {
86197
for (const name in data.optionalDependencies) {
87-
changes?.push(`optionalDependencies entry "${name}" was removed`)
198+
changes?.push(`optionalDependencies."${name}" was removed`)
88199
delete data.dependencies[name]
89200
}
90201
if (!Object.keys(data.dependencies).length) {
91-
changes?.push(`empty "optionalDependencies" was removed`)
202+
changes?.push(`Empty "optionalDependencies" was removed`)
92203
delete data.dependencies
93204
}
94205
}
@@ -121,20 +232,21 @@ const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase })
121232
}
122233

123234
// strip "node_modules/.bin" from scripts entries
235+
// remove invalid scripts entries (non-strings)
124236
if (steps.includes('scripts') || steps.includes('scriptpath')) {
125237
const spre = /^(\.[/\\])?node_modules[/\\].bin[\\/]/
126238
if (typeof data.scripts === 'object') {
127239
for (const name in data.scripts) {
128240
if (typeof data.scripts[name] !== 'string') {
129241
delete data.scripts[name]
130-
changes?.push(`invalid scripts entry "${name}" was removed`)
131-
} else if (steps.includes('scriptpath')) {
242+
changes?.push(`Invalid scripts."${name}" was removed`)
243+
} else if (steps.includes('scriptpath') && spre.test(data.scripts[name])) {
132244
data.scripts[name] = data.scripts[name].replace(spre, '')
133245
changes?.push(`scripts entry "${name}" was fixed to remove node_modules/.bin reference`)
134246
}
135247
}
136248
} else {
137-
changes?.push(`removed invalid "scripts"`)
249+
changes?.push(`Removed invalid "scripts"`)
138250
delete data.scripts
139251
}
140252
}
@@ -154,7 +266,7 @@ const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase })
154266
.map(line => line.replace(/^\s*#.*$/, '').trim())
155267
.filter(line => line)
156268
data.contributors = authors
157-
changes.push('"contributors" was auto-populated with the contents of the "AUTHORS" file')
269+
changes?.push('"contributors" was auto-populated with the contents of the "AUTHORS" file')
158270
} catch {
159271
// do nothing
160272
}
@@ -201,7 +313,7 @@ const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase })
201313
}
202314

203315
if (steps.includes('bin') || steps.includes('binDir') || steps.includes('binRefs')) {
204-
normalizePackageBin(data)
316+
normalizePackageBin(data, changes)
205317
}
206318

207319
// expand "directories.bin"
@@ -216,7 +328,7 @@ const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase })
216328
return acc
217329
}, {})
218330
// *sigh*
219-
normalizePackageBin(data)
331+
normalizePackageBin(data, changes)
220332
}
221333

222334
// populate "gitHead" attribute
@@ -320,22 +432,96 @@ const normalize = async (pkg, { strict, steps, root, changes, allowLegacyCase })
320432

321433
// Some steps are isolated so we can do a limited subset of these in `fix`
322434
if (steps.includes('fixRepositoryField') || steps.includes('normalizeData')) {
323-
legacyFixer.fixRepositoryField(data)
324-
}
325-
326-
if (steps.includes('fixBinField') || steps.includes('normalizeData')) {
327-
legacyFixer.fixBinField(data)
435+
if (data.repositories) {
436+
/* eslint-disable-next-line max-len */
437+
changes?.push(`"repository" was set to the first entry in "repositories" (${data.repository})`)
438+
data.repository = data.repositories[0]
439+
}
440+
if (data.repository) {
441+
if (typeof data.repository === 'string') {
442+
changes?.push('"repository" was changed from a string to an object')
443+
data.repository = {
444+
type: 'git',
445+
url: data.repository,
446+
}
447+
}
448+
if (data.repository.url) {
449+
const hosted = hostedGitInfo.fromUrl(data.repository.url)
450+
let r
451+
if (hosted) {
452+
if (hosted.getDefaultRepresentation() === 'shortcut') {
453+
r = hosted.https()
454+
} else {
455+
r = hosted.toString()
456+
}
457+
if (r !== data.repository.url) {
458+
changes?.push(`"repository.url" was normalized to "${r}"`)
459+
data.repository.url = r
460+
}
461+
}
462+
}
463+
}
328464
}
329465

330466
if (steps.includes('fixDependencies') || steps.includes('normalizeData')) {
331-
legacyFixer.fixDependencies(data, strict)
332-
}
467+
// peerDependencies?
468+
// devDependencies is meaningless here, it's ignored on an installed package
469+
for (const type of ['dependencies', 'devDependencies', 'optionalDependencies']) {
470+
if (data[type]) {
471+
let secondWarning = true
472+
if (typeof data[type] === 'string') {
473+
changes?.push(`"${type}" was converted from a string into an object`)
474+
data[type] = data[type].trim().split(/[\n\r\s\t ,]+/)
475+
secondWarning = false
476+
}
477+
if (Array.isArray(data[type])) {
478+
if (secondWarning) {
479+
changes?.push(`"${type}" was converted from an array into an object`)
480+
}
481+
const o = {}
482+
for (const d of data[type]) {
483+
if (typeof d === 'string') {
484+
const dep = d.trim().split(/(:?[@\s><=])/)
485+
const dn = dep.shift()
486+
const dv = dep.join('').replace(/^@/, '').trim()
487+
o[dn] = dv
488+
}
489+
}
490+
data[type] = o
491+
}
492+
}
493+
}
494+
// normalize-package-data used to put optional dependencies BACK into
495+
// dependencies here, we no longer do this
333496

334-
if (steps.includes('fixScriptsField') || steps.includes('normalizeData')) {
335-
legacyFixer.fixScriptsField(data)
497+
for (const deps of ['dependencies', 'devDependencies']) {
498+
if (deps in data) {
499+
if (!data[deps] || typeof data[deps] !== 'object') {
500+
changes?.push(`Removed invalid "${deps}"`)
501+
delete data[deps]
502+
} else {
503+
for (const d in data[deps]) {
504+
const r = data[deps][d]
505+
if (typeof r !== 'string') {
506+
changes?.push(`Removed invalid "${deps}.${d}"`)
507+
delete data[deps][d]
508+
}
509+
const hosted = hostedGitInfo.fromUrl(data[deps][d])?.toString()
510+
if (hosted && hosted !== data[deps][d]) {
511+
changes?.push(`Normalized git reference to "${deps}.${d}"`)
512+
data[deps][d] = hosted.toString()
513+
}
514+
}
515+
}
516+
}
517+
}
336518
}
337519

338520
if (steps.includes('normalizeData')) {
521+
legacyFixer.warn = function () {
522+
changes?.push(legacyMakeWarning.apply(null, arguments))
523+
}
524+
339525
const legacySteps = [
340526
'fixDescriptionField',
341527
'fixModulesField',

0 commit comments

Comments
 (0)