npq 3.7.1 → 3.8.1
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/__tests__/marshalls.provenance.test.js +1 -1
- package/__tests__/marshalls.repo.test.js +6 -6
- package/__tests__/marshalls.version-maturity.test.js +6 -6
- package/__tests__/packageRepoUtils.test.js +90 -1
- package/lib/helpers/packageRepoUtils.js +20 -5
- package/lib/marshalls/age.marshall.js +3 -3
- package/lib/marshalls/author.marshall.js +2 -2
- package/lib/marshalls/downloads.marshall.js +2 -2
- package/lib/marshalls/license.marshall.js +2 -2
- package/lib/marshalls/provenance.marshall.js +1 -1
- package/lib/marshalls/repo.marshall.js +4 -4
- package/lib/marshalls/scripts.marshall.js +1 -1
- package/lib/marshalls/signatures.marshall.js +1 -1
- package/lib/marshalls/version-maturity.marshall.js +3 -3
- package/lib/marshallsDecomissioned/readme.marshall.js +2 -2
- package/package.json +1 -1
|
@@ -196,7 +196,7 @@ describe('Provenance test suites', () => {
|
|
|
196
196
|
// We assert that the validate method didn't throw an error,
|
|
197
197
|
// because the keys match the signature
|
|
198
198
|
await expect(testMarshall.validate(pkg)).rejects.toThrow(
|
|
199
|
-
'the package was published without any attestations.'
|
|
199
|
+
'Unable to verify provenance: the package was published without any attestations.'
|
|
200
200
|
)
|
|
201
201
|
|
|
202
202
|
// Assert that the fetch method is called with the correct URL
|
|
@@ -37,7 +37,7 @@ describe('Repo test suites', () => {
|
|
|
37
37
|
|
|
38
38
|
test('throws the right error when there is no pkg data available', async () => {
|
|
39
39
|
await expect(testMarshall.validate({ packageName: {} })).rejects.toThrow(
|
|
40
|
-
'
|
|
40
|
+
'The package has no associated repository or homepage.'
|
|
41
41
|
)
|
|
42
42
|
})
|
|
43
43
|
|
|
@@ -54,7 +54,7 @@ describe('Repo test suites', () => {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
await expect(testMarshall.validate(pkgData)).rejects.toThrow(
|
|
57
|
-
'
|
|
57
|
+
'The package has no associated repository or homepage.'
|
|
58
58
|
)
|
|
59
59
|
})
|
|
60
60
|
|
|
@@ -73,7 +73,7 @@ describe('Repo test suites', () => {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
await expect(testMarshall.validate(pkgData)).rejects.toThrow(
|
|
76
|
-
'
|
|
76
|
+
'The package has no associated repository or homepage.'
|
|
77
77
|
)
|
|
78
78
|
})
|
|
79
79
|
|
|
@@ -81,7 +81,7 @@ describe('Repo test suites', () => {
|
|
|
81
81
|
global.fetch = jest.fn().mockImplementationOnce(() => Promise.reject(new Error('error')))
|
|
82
82
|
|
|
83
83
|
await expect(testMarshall.validate(fullPkgData)).rejects.toThrow(
|
|
84
|
-
'
|
|
84
|
+
'No valid repository is associated with the package'
|
|
85
85
|
)
|
|
86
86
|
})
|
|
87
87
|
|
|
@@ -91,7 +91,7 @@ describe('Repo test suites', () => {
|
|
|
91
91
|
fullPkgData.packageName.versions['1.0.0'].repository.url =
|
|
92
92
|
'https://dsfsdfsdfs.abcdeugwecwekjasda.com/'
|
|
93
93
|
await expect(testMarshall.validate(fullPkgData)).rejects.toThrow(
|
|
94
|
-
'
|
|
94
|
+
'The repository associated with the package (https://dsfsdfsdfs.abcdeugwecwekjasda.com/) does not exist or is unreachable at the moment.'
|
|
95
95
|
)
|
|
96
96
|
})
|
|
97
97
|
|
|
@@ -113,7 +113,7 @@ describe('Repo test suites', () => {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
await expect(testMarshall.validate(pkgData)).rejects.toThrow(
|
|
116
|
-
'
|
|
116
|
+
'The homepage associated with the package (homepage-url) does not exist or is unreachable at the moment.'
|
|
117
117
|
)
|
|
118
118
|
})
|
|
119
119
|
|
|
@@ -34,7 +34,7 @@ describe('Version Maturity Marshall', () => {
|
|
|
34
34
|
packageVersion: '1.0.0'
|
|
35
35
|
})
|
|
36
36
|
).rejects.toThrow(
|
|
37
|
-
'
|
|
37
|
+
'Detected a recently published version (published 2 hours ago) - consider waiting for community review'
|
|
38
38
|
)
|
|
39
39
|
})
|
|
40
40
|
|
|
@@ -61,7 +61,7 @@ describe('Version Maturity Marshall', () => {
|
|
|
61
61
|
packageVersion: '1.0.0'
|
|
62
62
|
})
|
|
63
63
|
).rejects.toThrow(
|
|
64
|
-
'
|
|
64
|
+
'Detected a recently published version (published 3 days ago) - consider waiting for community review'
|
|
65
65
|
)
|
|
66
66
|
})
|
|
67
67
|
|
|
@@ -88,7 +88,7 @@ describe('Version Maturity Marshall', () => {
|
|
|
88
88
|
packageVersion: '1.0.0'
|
|
89
89
|
})
|
|
90
90
|
).rejects.toThrow(
|
|
91
|
-
'
|
|
91
|
+
'Detected a recently published version (published 1 day ago) - consider waiting for community review'
|
|
92
92
|
)
|
|
93
93
|
})
|
|
94
94
|
|
|
@@ -135,7 +135,7 @@ describe('Version Maturity Marshall', () => {
|
|
|
135
135
|
packageName: 'test-package',
|
|
136
136
|
packageVersion: '1.0.0'
|
|
137
137
|
})
|
|
138
|
-
).rejects.toThrow('
|
|
138
|
+
).rejects.toThrow('Could not determine package version information')
|
|
139
139
|
})
|
|
140
140
|
|
|
141
141
|
test('should throw error when version release date is missing', async () => {
|
|
@@ -157,7 +157,7 @@ describe('Version Maturity Marshall', () => {
|
|
|
157
157
|
packageName: 'test-package',
|
|
158
158
|
packageVersion: '1.0.0'
|
|
159
159
|
})
|
|
160
|
-
).rejects.toThrow('
|
|
160
|
+
).rejects.toThrow('Could not determine release date for version 1.0.0')
|
|
161
161
|
})
|
|
162
162
|
|
|
163
163
|
test('should handle version aliases correctly', async () => {
|
|
@@ -188,7 +188,7 @@ describe('Version Maturity Marshall', () => {
|
|
|
188
188
|
packageVersion: 'latest'
|
|
189
189
|
})
|
|
190
190
|
).rejects.toThrow(
|
|
191
|
-
'
|
|
191
|
+
'Detected a recently published version (published 2 days ago) - consider waiting for community review'
|
|
192
192
|
)
|
|
193
193
|
})
|
|
194
194
|
})
|
|
@@ -157,6 +157,95 @@ test('repo utils returns valid semver for different cases of version asked', asy
|
|
|
157
157
|
expect(result).toEqual('3.1.0')
|
|
158
158
|
|
|
159
159
|
await expect(packageRepoUtils.getSemVer(packageName, 'next')).rejects.toThrow(
|
|
160
|
-
'
|
|
160
|
+
'Could not find dist-tag next for package testPackage'
|
|
161
|
+
)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test('repo utils resolves semver ranges by finding the highest satisfying version', async () => {
|
|
165
|
+
const PackageRepoUtils = require('../lib/helpers/packageRepoUtils')
|
|
166
|
+
global.fetch = jest.fn().mockImplementation(() =>
|
|
167
|
+
Promise.resolve({
|
|
168
|
+
json: () => require('./mocks/registryPackageOk.mock.json')
|
|
169
|
+
})
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
const packageRepoUtils = new PackageRepoUtils()
|
|
173
|
+
const packageName = 'testPackage'
|
|
174
|
+
|
|
175
|
+
// Test major version range - should find highest 3.x version (3.1.0)
|
|
176
|
+
let result = await packageRepoUtils.getSemVer(packageName, '^3.0.0')
|
|
177
|
+
expect(result).toEqual('3.1.0')
|
|
178
|
+
|
|
179
|
+
// Test simple major version - should find highest 3.x version (3.1.0)
|
|
180
|
+
result = await packageRepoUtils.getSemVer(packageName, '3')
|
|
181
|
+
expect(result).toEqual('3.1.0')
|
|
182
|
+
|
|
183
|
+
// Test tilde range - should find 3.1.x version (3.1.0)
|
|
184
|
+
result = await packageRepoUtils.getSemVer(packageName, '~3.1.0')
|
|
185
|
+
expect(result).toEqual('3.1.0')
|
|
186
|
+
|
|
187
|
+
// Test invalid semver range that doesn't match any version
|
|
188
|
+
await expect(packageRepoUtils.getSemVer(packageName, '^10.0.0')).rejects.toThrow(
|
|
189
|
+
'Could not find dist-tag ^10.0.0 for package testPackage'
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
// Test invalid semver range with version 2 (no 2.x versions in mock)
|
|
193
|
+
await expect(packageRepoUtils.getSemVer(packageName, '2')).rejects.toThrow(
|
|
194
|
+
'Could not find dist-tag 2 for package testPackage'
|
|
195
|
+
)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
test('repo utils resolves semver ranges with multiple versions', async () => {
|
|
199
|
+
const PackageRepoUtils = require('../lib/helpers/packageRepoUtils')
|
|
200
|
+
|
|
201
|
+
// Create a more comprehensive mock that includes multiple versions
|
|
202
|
+
const comprehensiveMock = {
|
|
203
|
+
name: '@astrojs/vue',
|
|
204
|
+
'dist-tags': {
|
|
205
|
+
latest: '4.5.0'
|
|
206
|
+
},
|
|
207
|
+
versions: {
|
|
208
|
+
'1.2.0': { name: '@astrojs/vue', version: '1.2.0' },
|
|
209
|
+
'2.0.0': { name: '@astrojs/vue', version: '2.0.0' },
|
|
210
|
+
'2.1.0': { name: '@astrojs/vue', version: '2.1.0' },
|
|
211
|
+
'2.2.1': { name: '@astrojs/vue', version: '2.2.1' },
|
|
212
|
+
'3.0.0': { name: '@astrojs/vue', version: '3.0.0' },
|
|
213
|
+
'3.1.0': { name: '@astrojs/vue', version: '3.1.0' },
|
|
214
|
+
'3.2.2': { name: '@astrojs/vue', version: '3.2.2' },
|
|
215
|
+
'4.0.0': { name: '@astrojs/vue', version: '4.0.0' },
|
|
216
|
+
'4.5.0': { name: '@astrojs/vue', version: '4.5.0' }
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
global.fetch = jest.fn().mockImplementation(() =>
|
|
221
|
+
Promise.resolve({
|
|
222
|
+
json: () => comprehensiveMock
|
|
223
|
+
})
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
const packageRepoUtils = new PackageRepoUtils()
|
|
227
|
+
const packageName = '@astrojs/vue'
|
|
228
|
+
|
|
229
|
+
// Test the problematic case mentioned in the issue: "@astrojs/vue@3"
|
|
230
|
+
let result = await packageRepoUtils.getSemVer(packageName, '3')
|
|
231
|
+
expect(result).toEqual('3.2.2') // Should find highest 3.x version
|
|
232
|
+
|
|
233
|
+
// Test other major versions
|
|
234
|
+
result = await packageRepoUtils.getSemVer(packageName, '2')
|
|
235
|
+
expect(result).toEqual('2.2.1') // Should find highest 2.x version
|
|
236
|
+
|
|
237
|
+
result = await packageRepoUtils.getSemVer(packageName, '4')
|
|
238
|
+
expect(result).toEqual('4.5.0') // Should find highest 4.x version
|
|
239
|
+
|
|
240
|
+
// Test caret ranges
|
|
241
|
+
result = await packageRepoUtils.getSemVer(packageName, '^3.0.0')
|
|
242
|
+
expect(result).toEqual('3.2.2')
|
|
243
|
+
|
|
244
|
+
result = await packageRepoUtils.getSemVer(packageName, '^2.1.0')
|
|
245
|
+
expect(result).toEqual('2.2.1')
|
|
246
|
+
|
|
247
|
+
// Test that non-existent major versions still fail appropriately
|
|
248
|
+
await expect(packageRepoUtils.getSemVer(packageName, '10')).rejects.toThrow(
|
|
249
|
+
'Could not find dist-tag 10 for package @astrojs/vue'
|
|
161
250
|
)
|
|
162
251
|
})
|
|
@@ -61,16 +61,31 @@ class PackageRepoUtils {
|
|
|
61
61
|
const packageInfo = await this.getPackageInfo(packageName)
|
|
62
62
|
|
|
63
63
|
if (packageInfo['dist-tags'] === undefined) {
|
|
64
|
-
throw new Error(`
|
|
64
|
+
throw new Error(`Could not find dist-tags for package ${packageName}`)
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
if (packageInfo['dist-tags'][packageVersion]
|
|
68
|
-
|
|
67
|
+
if (packageInfo['dist-tags'][packageVersion] !== undefined) {
|
|
68
|
+
const semverVersion = packageInfo['dist-tags'][packageVersion]
|
|
69
|
+
return semverVersion
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
|
|
72
|
+
// If not found in dist-tags, try to find the highest version that satisfies
|
|
73
|
+
// the semver range from the versions object
|
|
74
|
+
if (packageInfo.versions) {
|
|
75
|
+
const availableVersions = Object.keys(packageInfo.versions).filter((v) => semver.valid(v))
|
|
72
76
|
|
|
73
|
-
|
|
77
|
+
try {
|
|
78
|
+
const satisfyingVersion = semver.maxSatisfying(availableVersions, packageVersion)
|
|
79
|
+
|
|
80
|
+
if (satisfyingVersion) {
|
|
81
|
+
return satisfyingVersion
|
|
82
|
+
}
|
|
83
|
+
} catch (error) {
|
|
84
|
+
// semver.maxSatisfying throws if the range is invalid, continue to error below
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
throw new Error(`Could not find dist-tag ${packageVersion} for package ${packageName}`)
|
|
74
89
|
}
|
|
75
90
|
}
|
|
76
91
|
}
|
|
@@ -32,13 +32,13 @@ class Marshall extends BaseMarshall {
|
|
|
32
32
|
ageDateDiff = dateDiff
|
|
33
33
|
if (dateDiff < PACKAGE_AGE_THRESHOLD) {
|
|
34
34
|
throw new Error(
|
|
35
|
-
`
|
|
35
|
+
`Detected a newly published package (created < ${PACKAGE_AGE_THRESHOLD} days) act carefully`
|
|
36
36
|
)
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
return pkg
|
|
40
40
|
} else {
|
|
41
|
-
throw new Error('
|
|
41
|
+
throw new Error('Could not determine package age')
|
|
42
42
|
}
|
|
43
43
|
})
|
|
44
44
|
.then((pkg) => {
|
|
@@ -59,7 +59,7 @@ class Marshall extends BaseMarshall {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
if (versionDateDiffInDays >= PACKAGE_AGE_UNMAINTAINED_RISK) {
|
|
62
|
-
throw new Error(`
|
|
62
|
+
throw new Error(`Detected an old package (created ${timeAgoNumber} ${timeAgoText} ago)`)
|
|
63
63
|
}
|
|
64
64
|
})
|
|
65
65
|
}
|
|
@@ -43,11 +43,11 @@ class Marshall extends BaseMarshall {
|
|
|
43
43
|
// for dis-tags (latest)
|
|
44
44
|
const npmUser = pakument.versions[packageVersion]._npmUser
|
|
45
45
|
if (!npmUser || !npmUser.email) {
|
|
46
|
-
throw new Error('
|
|
46
|
+
throw new Error('Could not determine publishing user for this package version')
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
if (!validator.isEmail(npmUser.email)) {
|
|
50
|
-
throw new Error('
|
|
50
|
+
throw new Error('The publishing user has no valid email address')
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
let firstVersionForUser = null
|
|
@@ -23,13 +23,13 @@ class Marshall extends BaseMarshall {
|
|
|
23
23
|
return this.packageRepoUtils.getDownloadInfo(pkg.packageName).then((downloadCount) => {
|
|
24
24
|
if (downloadCount < DOWNLOAD_COUNT_THRESHOLD) {
|
|
25
25
|
throw new Error(
|
|
26
|
-
`
|
|
26
|
+
`Detected a low download-count package (downloads last month < ${DOWNLOAD_COUNT_THRESHOLD})`
|
|
27
27
|
)
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
if (downloadCount < DOWNLOAD_COUNT_UPPER_THRESHOLD) {
|
|
31
31
|
throw new Warning(
|
|
32
|
-
`
|
|
32
|
+
`Detected a low relatively low download-count package (${downloadCount} downloads last month)`
|
|
33
33
|
)
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -19,11 +19,11 @@ class Marshall extends BaseMarshall {
|
|
|
19
19
|
validate(pkg) {
|
|
20
20
|
return this.packageRepoUtils.getLicenseInfo(pkg.packageName).then((licenseContents) => {
|
|
21
21
|
if (!licenseContents || licenseContents === 'ERROR: No LICENSE data found!') {
|
|
22
|
-
throw new Error('
|
|
22
|
+
throw new Error('Package has no LICENSE file available')
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
if (licenseContents && licenseContents.indexOf('# Security holding package') === 0) {
|
|
26
|
-
throw new Error('
|
|
26
|
+
throw new Error('Package flagged for security issues and served as place-holder')
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
return licenseContents
|
|
@@ -30,21 +30,21 @@ class Marshall extends BaseMarshall {
|
|
|
30
30
|
urlStructure = new URL(lastVersionData.repository.url)
|
|
31
31
|
urlOfGitRepository = new URL(`https://${urlStructure.host}${urlStructure.pathname}`)
|
|
32
32
|
} catch (error) {
|
|
33
|
-
throw new Error('
|
|
33
|
+
throw new Error('No valid repository is associated with the package')
|
|
34
34
|
}
|
|
35
35
|
return fetch(urlOfGitRepository.href).catch(() => {
|
|
36
36
|
throw new Error(
|
|
37
|
-
`
|
|
37
|
+
`The repository associated with the package (${urlOfGitRepository.href}) does not exist or is unreachable at the moment.`
|
|
38
38
|
)
|
|
39
39
|
})
|
|
40
40
|
} else if (lastVersionData && lastVersionData.homepage) {
|
|
41
41
|
return fetch(lastVersionData.homepage).catch(() => {
|
|
42
42
|
throw new Error(
|
|
43
|
-
`
|
|
43
|
+
`The homepage associated with the package (${lastVersionData.homepage}) does not exist or is unreachable at the moment.`
|
|
44
44
|
)
|
|
45
45
|
})
|
|
46
46
|
} else {
|
|
47
|
-
throw new Error('
|
|
47
|
+
throw new Error('The package has no associated repository or homepage.')
|
|
48
48
|
}
|
|
49
49
|
})
|
|
50
50
|
}
|
|
@@ -43,7 +43,7 @@ class Marshall extends BaseMarshall {
|
|
|
43
43
|
packageScripts[scriptName].length > 0
|
|
44
44
|
) {
|
|
45
45
|
throw new Error(
|
|
46
|
-
`
|
|
46
|
+
`Detected a possible malicious intent script, act carefully: ${scriptName}: ${packageScripts[scriptName]}`
|
|
47
47
|
)
|
|
48
48
|
}
|
|
49
49
|
})
|
|
@@ -26,7 +26,7 @@ class Marshall extends BaseMarshall {
|
|
|
26
26
|
packageData = data
|
|
27
27
|
return pkg
|
|
28
28
|
} else {
|
|
29
|
-
throw new Error('
|
|
29
|
+
throw new Error('Could not determine package version information')
|
|
30
30
|
}
|
|
31
31
|
})
|
|
32
32
|
.then((pkg) => {
|
|
@@ -36,7 +36,7 @@ class Marshall extends BaseMarshall {
|
|
|
36
36
|
const versionReleaseDate = packageData.time[versionResolved]
|
|
37
37
|
|
|
38
38
|
if (!versionReleaseDate) {
|
|
39
|
-
throw new Error(`
|
|
39
|
+
throw new Error(`Could not determine release date for version ${versionResolved}`)
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
const versionDateDiff = new Date() - new Date(versionReleaseDate)
|
|
@@ -55,7 +55,7 @@ class Marshall extends BaseMarshall {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
throw new Error(
|
|
58
|
-
`
|
|
58
|
+
`Detected a recently published version (published ${timeAgoNumber} ${timeAgoText} ago) - consider waiting for community review`
|
|
59
59
|
)
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -19,11 +19,11 @@ class Marshall extends BaseMarshall {
|
|
|
19
19
|
validate(pkg) {
|
|
20
20
|
return this.packageRepoUtils.getReadmeInfo(pkg.packageName).then((readmeContents) => {
|
|
21
21
|
if (!readmeContents || readmeContents === 'ERROR: No README data found!') {
|
|
22
|
-
throw new Error('
|
|
22
|
+
throw new Error('Package has no README file available')
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
if (readmeContents && readmeContents.indexOf('# Security holding package') === 0) {
|
|
26
|
-
throw new Error('
|
|
26
|
+
throw new Error('Package flagged for security issues and served as place-holder')
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
return readmeContents
|