Created
August 1, 2024 16:22
-
-
Save unarist/197eacba0d470b83b2df9309ac7da313 to your computer and use it in GitHub Desktop.
npm audit の結果を依存パスと共に表示するやつ
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//> using scala 3.3.1 | |
//> using toolkit default | |
import os.RelPath | |
import upickle.default.* | |
opaque type PackageName = String | |
opaque type PackageVersion = String | |
opaque type Severity = String | |
opaque type ModulePath = String | |
implicit def stringOrRW[T: ReadWriter]: ReadWriter[String | T] = | |
readwriter[ujson.Value].bimap[String | T]( | |
{ | |
case a: String => writeJs(a) | |
case b => writeJs(b) | |
}, | |
{ | |
case ujson.Str(str) => str | |
case json => read[T](json) | |
} | |
) | |
case class AuditResult(vulnerabilities: Map[PackageName, Vulnerability]) derives ReadWriter | |
case class Vulnerability(name: PackageName, severity: Severity, via: List[String | VulnerabilityInfo], nodes: List[ModulePath]) derives ReadWriter | |
case class VulnerabilityInfo(title: String, severity: Severity) derives ReadWriter | |
case class ListResult(dependencies: Map[PackageName, Dependency]) derives ReadWriter | |
case class Dependency(version: PackageVersion, dependencies: Map[PackageName, Dependency] = Map.empty) derives ReadWriter | |
case class PackageJson(name: PackageName, version: PackageVersion) derives ReadWriter | |
case class PackageNameAndVersion(name: PackageName, version: PackageVersion) { | |
override def toString(): String = s"$name@$version" | |
} | |
def runNpmAudit(): AuditResult = { | |
val auditResult = os.proc("npm", "audit", "--json").call(check = false).out.text() | |
read[AuditResult](auditResult) | |
} | |
def runNpmList(name: PackageName): ListResult = { | |
val listResult = os.proc("npm", "list", "--json", name).call().out.text() | |
read[ListResult](listResult) | |
} | |
def readPackageJson(modulePath: ModulePath): PackageJson = { | |
val packageJson = os.read(os.pwd / RelPath(modulePath) / "package.json") | |
read[PackageJson](packageJson) | |
} | |
def why(name: PackageName, version: String): Seq[Seq[PackageNameAndVersion]] = { | |
val rootDeps = runNpmList(name).dependencies | |
def traverse(deps: Map[PackageName, Dependency], parents: List[PackageNameAndVersion] = Nil): Seq[Seq[PackageNameAndVersion]] = { | |
deps.flatMap { case (depName, dep) => | |
if (depName == name && dep.version == version) { | |
Seq(parents.reverse :+ PackageNameAndVersion(depName, dep.version)) | |
} else { | |
traverse(dep.dependencies, PackageNameAndVersion(depName, dep.version) :: parents) | |
} | |
}.toSeq | |
} | |
traverse(rootDeps) | |
} | |
for (vuln <- runNpmAudit().vulnerabilities.values.filter(_.via.exists(_.isInstanceOf[VulnerabilityInfo]))) { | |
val lines = for { | |
modulePath <- vuln.nodes | |
packageJson = readPackageJson(modulePath) | |
reason <- why(packageJson.name, packageJson.version) | |
} yield { | |
val shortReason = if (reason.length > 3) reason.take(2) ++ Seq("...") ++ reason.takeRight(1) else reason | |
val title = vuln.via.collectFirst { case v: VulnerabilityInfo if v.severity == vuln.severity => v.title } | |
.orElse(vuln.via.collectFirst { case v: VulnerabilityInfo => v.title }) | |
.getOrElse("") | |
val andmore = vuln.via.count(_.isInstanceOf[VulnerabilityInfo]) - 1 | |
s"${shortReason.mkString(" :: ")}\t${vuln.severity}\t${title}${if (andmore > 0) s" (and $andmore more)" else ""}" | |
} | |
lines.distinct.foreach(println) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment