npq 3.16.0 → 3.16.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.
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Calculates the Levenshtein distance between two strings.
|
|
5
|
+
* Uses the Wagner-Fischer algorithm with single-row space optimization
|
|
6
|
+
* and optional early termination when distance exceeds maxDistance.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} a - First string
|
|
9
|
+
* @param {string} b - Second string
|
|
10
|
+
* @param {number} [maxDistance] - Optional maximum distance threshold for early termination
|
|
11
|
+
* @returns {number} The Levenshtein edit distance between the two strings
|
|
12
|
+
*/
|
|
13
|
+
function levenshteinDistance(a, b, maxDistance) {
|
|
14
|
+
// Handle edge cases
|
|
15
|
+
if (a === b) return 0
|
|
16
|
+
if (a.length === 0) return b.length
|
|
17
|
+
if (b.length === 0) return a.length
|
|
18
|
+
|
|
19
|
+
// Ensure a is the shorter string for space optimization
|
|
20
|
+
if (a.length > b.length) {
|
|
21
|
+
const temp = a
|
|
22
|
+
a = b
|
|
23
|
+
b = temp
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const aLen = a.length
|
|
27
|
+
const bLen = b.length
|
|
28
|
+
|
|
29
|
+
// Early termination: if length difference exceeds maxDistance, no need to compute
|
|
30
|
+
if (maxDistance !== undefined && bLen - aLen >= maxDistance) {
|
|
31
|
+
return bLen - aLen
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Initialize the previous row (represents distances for empty string a prefix)
|
|
35
|
+
let prevRow = new Array(aLen + 1)
|
|
36
|
+
for (let i = 0; i <= aLen; i++) {
|
|
37
|
+
prevRow[i] = i
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Current row for computation
|
|
41
|
+
let currRow = new Array(aLen + 1)
|
|
42
|
+
|
|
43
|
+
// Fill in the matrix row by row
|
|
44
|
+
for (let j = 1; j <= bLen; j++) {
|
|
45
|
+
currRow[0] = j
|
|
46
|
+
|
|
47
|
+
let rowMin = currRow[0]
|
|
48
|
+
|
|
49
|
+
for (let i = 1; i <= aLen; i++) {
|
|
50
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1
|
|
51
|
+
|
|
52
|
+
currRow[i] = Math.min(
|
|
53
|
+
prevRow[i] + 1, // deletion
|
|
54
|
+
currRow[i - 1] + 1, // insertion
|
|
55
|
+
prevRow[i - 1] + cost // substitution
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if (currRow[i] < rowMin) {
|
|
59
|
+
rowMin = currRow[i]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Early termination: if minimum value in current row exceeds maxDistance,
|
|
64
|
+
// the final distance will also exceed maxDistance
|
|
65
|
+
if (maxDistance !== undefined && rowMin >= maxDistance) {
|
|
66
|
+
return rowMin
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Swap rows
|
|
70
|
+
const temp = prevRow
|
|
71
|
+
prevRow = currRow
|
|
72
|
+
currRow = temp
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return prevRow[aLen]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = { levenshteinDistance }
|
|
@@ -12,9 +12,20 @@ async function getProjectPackages() {
|
|
|
12
12
|
const packageJSON = await fs.readFile(packageJSONPath, 'utf-8')
|
|
13
13
|
const packageData = JSON.parse(packageJSON)
|
|
14
14
|
if (packageData && packageData.dependencies) {
|
|
15
|
-
packages =
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
packages = [
|
|
16
|
+
...packages,
|
|
17
|
+
...Object.keys(packageData.dependencies).map((dep) => {
|
|
18
|
+
return `${dep}@${packageData.dependencies[dep]}`
|
|
19
|
+
})
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
if (packageData && packageData.devDependencies) {
|
|
23
|
+
packages = [
|
|
24
|
+
...packages,
|
|
25
|
+
...Object.keys(packageData.devDependencies).map((dep) => {
|
|
26
|
+
return `${dep}@${packageData.devDependencies[dep]}`
|
|
27
|
+
})
|
|
28
|
+
]
|
|
18
29
|
}
|
|
19
30
|
|
|
20
31
|
return packages
|
|
@@ -4,7 +4,7 @@ const BaseMarshall = require('./baseMarshall')
|
|
|
4
4
|
const { marshallCategories } = require('./constants')
|
|
5
5
|
|
|
6
6
|
const path = require('path')
|
|
7
|
-
const {
|
|
7
|
+
const { levenshteinDistance } = require('../helpers/levenshteinDistance')
|
|
8
8
|
const topPackagesRawJSON = require(path.join(__dirname, '../../data/top-packages.json'))
|
|
9
9
|
|
|
10
10
|
const MARSHALL_NAME = 'typosquatting'
|
|
@@ -21,7 +21,7 @@ class Marshall extends BaseMarshall {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
validate(pkg) {
|
|
24
|
-
let
|
|
24
|
+
let editDistance = null
|
|
25
25
|
let similarPackages = []
|
|
26
26
|
return new Promise((resolve, reject) => {
|
|
27
27
|
// If package is within an allow-list
|
|
@@ -36,9 +36,9 @@ class Marshall extends BaseMarshall {
|
|
|
36
36
|
return resolve([])
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
editDistance = levenshteinDistance(pkg.packageName, popularPackageNameInRepository)
|
|
40
40
|
|
|
41
|
-
if (
|
|
41
|
+
if (editDistance > 0 && editDistance < 3) {
|
|
42
42
|
similarPackages.push(popularPackageNameInRepository)
|
|
43
43
|
}
|
|
44
44
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "npq",
|
|
3
|
-
"version": "3.16.
|
|
3
|
+
"version": "3.16.1",
|
|
4
4
|
"description": "marshall your npm/npm package installs with high quality and class 🎖",
|
|
5
5
|
"bin": {
|
|
6
6
|
"npq": "./bin/npq.js",
|
|
@@ -43,7 +43,6 @@
|
|
|
43
43
|
"access": "public"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"fastest-levenshtein": "^1.0.16",
|
|
47
46
|
"npm-package-arg": "^13.0.2",
|
|
48
47
|
"semver": "^7.7.3",
|
|
49
48
|
"sigstore": "^3.1.0",
|