From 04e7ad32cc8fc261b41fc76e7683ff45a5402466 Mon Sep 17 00:00:00 2001 From: WarningImHack3r <43064022+WarningImHack3r@users.noreply.github.com> Date: Sat, 28 Sep 2024 16:13:46 +0200 Subject: [PATCH] Use npm ls to batch registries scan if possible Fixes #136 again --- CHANGELOG.md | 2 + .../backend/engine/RegistriesScanner.kt | 50 ++++++++++++++++++- 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 039a265..5ddb8ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ ### Added - Add HTTP caching for some requests to the npm registry, improving performance (#135) +- Attempt to batch registries scan without network requests before falling back to the original behavior, speeding up the first scan (#136) + - Many thanks to [@SCjona](https://github.com/SCjona) for the whole 3.1.0 and the two issues of this release! ### Fixed diff --git a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/RegistriesScanner.kt b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/RegistriesScanner.kt index ef2e839..90ba508 100644 --- a/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/RegistriesScanner.kt +++ b/src/main/kotlin/com/github/warningimhack3r/npmupdatedependencies/backend/engine/RegistriesScanner.kt @@ -1,9 +1,12 @@ package com.github.warningimhack3r.npmupdatedependencies.backend.engine +import com.github.warningimhack3r.npmupdatedependencies.backend.extensions.asJsonObject +import com.github.warningimhack3r.npmupdatedependencies.backend.extensions.asString import com.intellij.openapi.components.Service import com.intellij.openapi.components.service import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project +import kotlinx.serialization.json.Json @Service(Service.Level.PROJECT) class RegistriesScanner(private val project: Project) { @@ -26,8 +29,53 @@ class RegistriesScanner(private val project: Project) { fun scan() { log.info("Starting to scan registries") + val shellRunner = ShellRunner.getInstance(project) + val state = NUDState.getInstance(project) + // Populate packageRegistries with the contents of `npm ls --json` + shellRunner.execute(arrayOf("npm", "ls", "--json"))?.let { json -> + val jsonElement = Json.parseToJsonElement(json).asJsonObject ?: return@let.also { + log.warn("Failed to parse JSON from npm ls --json") + } + val dependencies = jsonElement["dependencies"]?.asJsonObject ?: return@let.also { + log.warn("No dependencies found in JSON from npm ls --json") + } + var registriesSet = setOf() + for (packageName in dependencies.keys) { + val values = dependencies[packageName]?.asJsonObject ?: continue.also { + log.warn("No values found for package $packageName in JSON from npm ls --json") + } + if (values.keys.contains("extraneous")) { + log.debug("Package $packageName is extraneous, skipping") + continue + } + val registry = dependencies[packageName]?.asJsonObject?.get("resolved")?.asString ?: continue.also { + log.warn("No resolved found for package $packageName in JSON from npm ls --json") + } + if (!registry.startsWith("http")) { + log.warn("Invalid registry $registry for package $packageName, skipping") + continue + } + val formattedRegistry = registry.substringBefore("/$packageName") + log.debug("Found registry $formattedRegistry for package $packageName") + registriesSet += formattedRegistry + state.packageRegistries[packageName] = formattedRegistry + } + registries = registriesSet.toList() + } + if (registries.isNotEmpty()) { + // There are extra thin chances of missing registries with + // this logic, like when a package with a custom registry is + // installed with pnpm BUT at least one other is installed + // with npm. + // We'll consider here such edge cases negligible (and the user's + // responsibility), prioritizing speed and simplicity instead of + // slowing things down for everyone because of a few weird cases. + log.info("Found registries from `npm ls`: $registries") + if (!scanned) scanned = true + return + } // Run `npm config ls` to get the list of registries - val config = ShellRunner.getInstance(project).execute(arrayOf("npm", "config", "ls")) ?: return + val config = shellRunner.execute(arrayOf("npm", "config", "ls")) ?: return registries = config.lines().asSequence().map { it.trim() }.filter { line -> line.isNotEmpty() && line.isNotBlank() && !line.startsWith(";") && (line.contains("registry =") || line.contains("registry=")