npq 3.27.2 → 3.27.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/lib/cli.js +1 -2
- package/lib/helpers/packageRepoUtils.js +8 -34
- package/lib/helpers/registryClient.js +9 -8
- package/lib/helpers/resolveManifest.js +41 -0
- package/lib/marshalls/author.marshall.js +0 -2
- package/lib/marshalls/baseMarshall.js +5 -21
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -50,6 +50,12 @@ npq install express
|
|
|
50
50
|
|
|
51
51
|
When npq completes its signal checks it hands over the actual package install job to the package manager (npm by default, or as specified via the `NPQ_PKG_MGR` environment variable).
|
|
52
52
|
|
|
53
|
+
Registry-manifest checks use npm's selection rules for versions, ranges, and tags.
|
|
54
|
+
If an enabled registry-manifest check cannot resolve the requested version, the
|
|
55
|
+
audit fails. The Snyk/OSV vulnerability check handles selectors separately. These
|
|
56
|
+
checks do not pin the subsequent install: lockfiles, package-manager-specific
|
|
57
|
+
resolution, and registry changes after the audit can affect what is installed.
|
|
58
|
+
|
|
53
59
|
**DISCLAIMER**: there's no guaranteed absolute safety; a malicious or vulnerable package could still exist that has no security vulnerabilities publicly disclosed and passes npq's checks.
|
|
54
60
|
|
|
55
61
|
## Demo
|
package/lib/cli.js
CHANGED
|
@@ -64,8 +64,7 @@ class CliParser {
|
|
|
64
64
|
// Parse and normalize packages
|
|
65
65
|
return packages.map((pkg) => {
|
|
66
66
|
const parsedPackage = json ? parseJsonRegistryPackageSpec(pkg) : npa(pkg)
|
|
67
|
-
|
|
68
|
-
return `${parsedPackage.name}@${versionModifier}`
|
|
67
|
+
return `${parsedPackage.name}@${parsedPackage.fetchSpec}`
|
|
69
68
|
})
|
|
70
69
|
}
|
|
71
70
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const semver = require('semver')
|
|
4
4
|
const RegistryClient = require('./registryClient')
|
|
5
|
+
const resolveManifest = require('./resolveManifest')
|
|
5
6
|
|
|
6
7
|
class PackageRepoUtils {
|
|
7
8
|
constructor({ registryClient = RegistryClient.public() } = {}) {
|
|
@@ -48,40 +49,13 @@ class PackageRepoUtils {
|
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
async getSemVer(packageName, packageVersion) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (packageInfo['dist-tags'] === undefined) {
|
|
59
|
-
throw new Error(`Could not find dist-tags for package ${packageName}`)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (packageInfo['dist-tags'][packageVersion] !== undefined) {
|
|
63
|
-
const semverVersion = packageInfo['dist-tags'][packageVersion]
|
|
64
|
-
return semverVersion
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// If not found in dist-tags, try to find the highest version that satisfies
|
|
68
|
-
// the semver range from the versions object
|
|
69
|
-
if (packageInfo.versions) {
|
|
70
|
-
const availableVersions = Object.keys(packageInfo.versions).filter((v) => semver.valid(v))
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
const satisfyingVersion = semver.maxSatisfying(availableVersions, packageVersion)
|
|
74
|
-
|
|
75
|
-
if (satisfyingVersion) {
|
|
76
|
-
return satisfyingVersion
|
|
77
|
-
}
|
|
78
|
-
} catch {
|
|
79
|
-
// semver.maxSatisfying throws if the range is invalid, continue to error below
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
throw new Error(`Could not find dist-tag ${packageVersion} for package ${packageName}`)
|
|
84
|
-
}
|
|
52
|
+
const packageInfo = await this.getPackageInfo(packageName)
|
|
53
|
+
return resolveManifest(
|
|
54
|
+
packageName,
|
|
55
|
+
packageVersion,
|
|
56
|
+
packageInfo,
|
|
57
|
+
this.registryClient.registryConfig?.requestOptions
|
|
58
|
+
).version
|
|
85
59
|
}
|
|
86
60
|
|
|
87
61
|
/**
|
|
@@ -5,6 +5,7 @@ const npmFetch = require('npm-registry-fetch')
|
|
|
5
5
|
const NotEvaluated = require('./notEvaluated')
|
|
6
6
|
const RegistryConfig = require('./registryConfig')
|
|
7
7
|
const { RegistryError } = require('./registryErrors')
|
|
8
|
+
const resolveManifest = require('./resolveManifest')
|
|
8
9
|
|
|
9
10
|
const PUBLIC_REGISTRY = 'https://registry.npmjs.org/'
|
|
10
11
|
const OPTIONAL_STATUS = new Set([404, 405, 501])
|
|
@@ -96,15 +97,15 @@ class RegistryClient {
|
|
|
96
97
|
async getManifest(packageSpec, packument = null) {
|
|
97
98
|
const spec = npa(packageSpec)
|
|
98
99
|
const data = packument || (await this.getPackageInfo(spec.name))
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
100
|
+
const manifest = resolveManifest(
|
|
101
|
+
spec.name,
|
|
102
|
+
spec.fetchSpec,
|
|
103
|
+
data,
|
|
104
|
+
this.registryConfig.requestOptions
|
|
105
|
+
)
|
|
106
|
+
const version = manifest.version
|
|
106
107
|
return {
|
|
107
|
-
...
|
|
108
|
+
...manifest,
|
|
108
109
|
...(data.time && data.time[version] ? { _time: data.time[version] } : {})
|
|
109
110
|
}
|
|
110
111
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const npa = require('npm-package-arg')
|
|
4
|
+
const pickManifest = require('npm-pick-manifest')
|
|
5
|
+
const { RegistryError } = require('./registryErrors')
|
|
6
|
+
|
|
7
|
+
// Selection must follow npm's spec classification and manifest preferences. In
|
|
8
|
+
// particular, a semver-shaped dist-tag must never override a version or range.
|
|
9
|
+
function resolveManifest(packageName, versionSpec, packument, options = {}) {
|
|
10
|
+
try {
|
|
11
|
+
if (typeof versionSpec !== 'string' || versionSpec.trim() === '') {
|
|
12
|
+
throw new Error('Missing version specification')
|
|
13
|
+
}
|
|
14
|
+
const spec = npa.resolve(packageName, versionSpec)
|
|
15
|
+
if (!['version', 'range', 'tag'].includes(spec.type)) {
|
|
16
|
+
throw new Error('Only registry versions, ranges, and tags can be audited')
|
|
17
|
+
}
|
|
18
|
+
if (!packument || !packument.versions) {
|
|
19
|
+
throw new Error('Missing package versions')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// npm-pick-manifest normalizes bin fields in place. Keep the cached
|
|
23
|
+
// packument unchanged so every marshall sees the original metadata.
|
|
24
|
+
const manifest = pickManifest(
|
|
25
|
+
structuredClone({ ...packument, name: packageName }),
|
|
26
|
+
spec.fetchSpec,
|
|
27
|
+
options
|
|
28
|
+
)
|
|
29
|
+
if (!manifest.version || !packument.versions[manifest.version]) {
|
|
30
|
+
throw new Error('Selected manifest has no matching package version')
|
|
31
|
+
}
|
|
32
|
+
return manifest
|
|
33
|
+
} catch (cause) {
|
|
34
|
+
throw new RegistryError(`Unable to resolve version ${versionSpec} for package ${packageName}`, {
|
|
35
|
+
code: 'EREGISTRYRESOLVE',
|
|
36
|
+
cause
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = resolveManifest
|
|
@@ -138,8 +138,6 @@ class Marshall extends BaseMarshall {
|
|
|
138
138
|
pkg.packageVersion
|
|
139
139
|
)
|
|
140
140
|
|
|
141
|
-
// @TODO fix to work for both explicit versions (1.0.0) and also
|
|
142
|
-
// for dist-tags (latest)
|
|
143
141
|
const npmUser = pakument.versions[packageVersion]._npmUser
|
|
144
142
|
if (!npmUser || !npmUser.email) {
|
|
145
143
|
throw new Error('Could not determine publishing user for this package version')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const resolveManifest = require('../helpers/resolveManifest')
|
|
4
4
|
const util = require('node:util')
|
|
5
5
|
const NotEvaluated = require('../helpers/notEvaluated')
|
|
6
6
|
const { RegistryError } = require('../helpers/registryErrors')
|
|
@@ -107,30 +107,14 @@ class BaseMarshall {
|
|
|
107
107
|
* @param {string} packageName - The package name
|
|
108
108
|
* @param {string} versionSpec - The version specification (could be dist-tag or semver)
|
|
109
109
|
* @param {Object} packageData - Optional package data to avoid re-fetching
|
|
110
|
-
* @returns {Promise<string
|
|
110
|
+
* @returns {Promise<string>} - The resolved semver version; rejects on resolution failure
|
|
111
111
|
*/
|
|
112
112
|
|
|
113
113
|
async resolvePackageVersion(packageName, versionSpec, packageData) {
|
|
114
|
-
if (typeof versionSpec !== 'string' || versionSpec.trim() === '') return null
|
|
115
114
|
const data = packageData || (await this.packageRepoUtils.getPackageInfo(packageName))
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
// Handle dist-tags first
|
|
120
|
-
if (data['dist-tags'] && data['dist-tags'][versionSpec]) {
|
|
121
|
-
return data['dist-tags'][versionSpec]
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Handle semver ranges and exact versions using all available versions
|
|
125
|
-
if (data.versions) {
|
|
126
|
-
const availableVersions = Object.keys(data.versions)
|
|
127
|
-
const resolved = semver.maxSatisfying(availableVersions, versionSpec)
|
|
128
|
-
if (resolved) return resolved
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Fallback for simple parsing if range resolution fails
|
|
132
|
-
const parsed = this.packageRepoUtils.parsePackageVersion(versionSpec)
|
|
133
|
-
return parsed ? parsed.version : null
|
|
115
|
+
const client = this.registryClient || this.packageRepoUtils.registryClient
|
|
116
|
+
return resolveManifest(packageName, versionSpec, data, client?.registryConfig?.requestOptions)
|
|
117
|
+
.version
|
|
134
118
|
}
|
|
135
119
|
}
|
|
136
120
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "npq",
|
|
3
|
-
"version": "3.27.
|
|
3
|
+
"version": "3.27.4",
|
|
4
4
|
"description": "marshall your npm/npm package installs with high quality and class 🎖",
|
|
5
5
|
"bin": {
|
|
6
6
|
"npq": "./bin/npq.js",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"@npmcli/config": "^10.10.0",
|
|
48
48
|
"cross-spawn": "^7.0.6",
|
|
49
49
|
"npm-package-arg": "^13.0.2",
|
|
50
|
+
"npm-pick-manifest": "^11.0.3",
|
|
50
51
|
"npm-registry-fetch": "^19.1.1",
|
|
51
52
|
"semver": "^7.7.4",
|
|
52
53
|
"sigstore": "^4.1.0",
|
|
@@ -93,5 +94,6 @@
|
|
|
93
94
|
}
|
|
94
95
|
]
|
|
95
96
|
}
|
|
96
|
-
}
|
|
97
|
+
},
|
|
98
|
+
"packageManager": "npm@11.21.0"
|
|
97
99
|
}
|