Skip to content

Commit

Permalink
Update README, getInstance(), format & logging
Browse files Browse the repository at this point in the history
  • Loading branch information
WarningImHack3r committed Apr 17, 2024
1 parent 7c5ec4a commit c662b9d
Show file tree
Hide file tree
Showing 18 changed files with 220 additions and 65 deletions.
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,31 @@
[![Downloads](https://img.shields.io/jetbrains/plugin/d/com.github.warningimhack3r.npmupdatedependencies.svg)](https://plugins.jetbrains.com/plugin/com.github.warningimhack3r.npmupdatedependencies)

## Description

<!-- Plugin description -->
Update your npm dependencies with a single click.

This plugin will update all the dependencies in your `package.json` file to the latest version, or the satisfying version depending on your choice.
This plugin will update all the dependencies in your `package.json` file to the latest version, or the satisfying
version depending on your choice.

## Features

- Update a dependency to the latest or satisfying version
- Support for custom registries and private packages
- Keep comparators (e.g. `^`, `~`, `>`, `<`, `>=`, `<=`) when replacing versions
- Detect and replace/remove deprecated dependencies
- Batch update all dependencies (latest or satisfying), all deprecated dependencies
- Get notified of deprecated dependencies with a banner
- See your outdated dependencies at a glance in the status bar
- Exclude dependencies or versions from the scan
- Configure everything in the settings
- Manually invalidate the cache in case of issues
- ...and more!

## Usage

There are 3 ways to invoke the extension menu:

- Hover over an annotated dependency and click the action you want to perform
- Right click in the package.json file and select the extension from the context menu
- Use the Tools menu
Expand All @@ -33,25 +38,22 @@ Configuration options are available in the settings.

> Works by fetching [registry.npmjs.org](https://registry.npmjs.org).
> Rewrite of the existing [npm-dependency-checker](https://github.com/unger1984/npm-dependency-checker) plugin.
## Planned Features
- Show changelog for each dependency
- Automatically install upgraded dependencies when saving the package.json file?
<!-- Plugin description end -->
## Installation

- Using IDE built-in plugin system:

<kbd>Settings/Preferences</kbd> > <kbd>Plugins</kbd> > <kbd>Marketplace</kbd> > <kbd>Search for "npm-update-dependencies"</kbd> >

<kbd>Settings/Preferences</kbd> > <kbd>Plugins</kbd> > <kbd>Marketplace</kbd> > <kbd>Search for "
npm-update-dependencies"</kbd> >
<kbd>Install Plugin</kbd>

- Manually:

Download the [latest release](https://github.com/WarningImHack3r/npm-update-dependencies/releases/latest) and install it manually using
Download the [latest release](https://github.com/WarningImHack3r/npm-update-dependencies/releases/latest) and install
it manually using
<kbd>Settings/Preferences</kbd> > <kbd>Plugins</kbd> > <kbd>⚙️</kbd> > <kbd>Install plugin from disk...</kbd>


---
Plugin based on the [IntelliJ Platform Plugin Template][template].

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import com.google.gson.JsonObject
import com.google.gson.JsonParser
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 com.jetbrains.rd.util.printlnError
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
Expand All @@ -16,27 +16,46 @@ import java.net.http.HttpResponse
class NPMJSClient(private val project: Project) {
companion object {
private const val NPMJS_REGISTRY = "https://registry.npmjs.org"
private val log = logger<NPMJSClient>()

@JvmStatic
fun getInstance(project: Project): NPMJSClient = project.service()
}

private fun getRegistry(packageName: String): String {
val registryForPackage = project.service<NUDState>().packageRegistries
val availableRegistries = project.service<RegistriesScanner>().registries
return registryForPackage[packageName] ?: ShellRunner.execute(
log.info("Getting registry for package $packageName")
val registryForPackage = NUDState.getInstance(project).packageRegistries
val availableRegistries = RegistriesScanner.getInstance(project).registries
return registryForPackage[packageName].also {
if (it != null) {
log.debug("Registry for package $packageName found in cache: $it")
}
} ?: ShellRunner.execute(
arrayOf("npm", "v", packageName, "dist.tarball")
)?.trim()?.let { dist ->
val computedRegistry = dist.ifEmpty {
log.debug("No dist.tarball found for package $packageName, trying all registries")
availableRegistries.parallelMap { registry ->
ShellRunner.execute(
arrayOf("npm", "v", packageName, "dist.tarball", "--registry=$registry")
)?.trim()?.let { regDist ->
regDist.ifEmpty { null }
}
}.firstNotNullOfOrNull { it } ?: return@let null
}.firstNotNullOfOrNull { it }.also {
if (it != null) {
log.debug("Found dist.tarball for package $packageName in registry $it")
}
} ?: return@let null.also {
log.debug("No dist.tarball found for package $packageName in any registry")
}
}
val registry = "${computedRegistry.substringBefore("/$packageName")}/"
log.info("Computed registry for package $packageName: $registry")
registryForPackage[packageName] = registry
registry
} ?: NPMJS_REGISTRY
} ?: NPMJS_REGISTRY.also {
log.info("Using default registry for package $packageName")
}
}

private fun getResponseBody(uri: URI): String {
Expand All @@ -53,7 +72,7 @@ class NPMJSClient(private val project: Project) {
try {
responseBody = getResponseBody(URI(uri))
} catch (e: Exception) {
printlnError("Error while getting response body from $uri: ${e.message}")
log.warn("Error while getting response body from $uri", e)
return null
}
return try {
Expand All @@ -63,40 +82,72 @@ class NPMJSClient(private val project: Project) {
null
}
} catch (e: Exception) {
printlnError("Error while parsing response body from $uri: ${e.message}")
log.warn("Error while parsing response body from $uri", e)
null
}
}

fun getLatestVersion(packageName: String): String? {
log.info("Getting latest version for package $packageName")
val registry = getRegistry(packageName)
val json = getBodyAsJSON("${registry}/$packageName/latest")
return json?.get("version")?.asString ?: ShellRunner.execute(
return json?.get("version")?.asString.also {
if (it != null) {
log.info("Latest version for package $packageName found in cache: $it")
}
} ?: ShellRunner.execute(
arrayOf("npm", "v", packageName, "version", "--registry=$registry")
)?.trim()?.let { it.ifEmpty { null } }
)?.trim()?.let { it.ifEmpty { null } }.also {
if (it != null) {
log.info("Latest version for package $packageName found: $it")
} else {
log.warn("Latest version for package $packageName not found")
}
}
}

fun getAllVersions(packageName: String): List<String>? {
log.info("Getting all versions for package $packageName")
val registry = getRegistry(packageName)
val json = getBodyAsJSON("${registry}/$packageName")
return json?.get("versions")?.asJsonObject?.keySet()?.toList() ?: ShellRunner.execute(
return json?.get("versions")?.asJsonObject?.keySet()?.toList().also {
if (it != null) {
log.info("All versions for package $packageName found in cache: $it")
}
} ?: ShellRunner.execute(
arrayOf("npm", "v", packageName, "versions", "--json", "--registry=$registry")
)?.trim()?.let { versions ->
if (versions.isEmpty()) {
log.warn("All versions for package $packageName not found")
return null
} else if (versions.startsWith("[")) {
JsonParser.parseString(versions).asJsonArray.map { it.asString }
} else {
listOf(versions.replace("\"", ""))
}
}.also { versions ->
if (versions != null) {
log.info("All versions for package $packageName found: $versions")
}
}
}

fun getPackageDeprecation(packageName: String): String? {
log.info("Getting deprecation status for package $packageName")
val registry = getRegistry(packageName)
val json = getBodyAsJSON("${registry}/$packageName/latest")
return json?.get("deprecated")?.asString ?: ShellRunner.execute(
return json?.get("deprecated")?.asString.also {
if (it != null) {
log.info("Deprecation status for package $packageName found in cache: $it")
}
} ?: ShellRunner.execute(
arrayOf("npm", "v", packageName, "deprecated", "--registry=$registry")
)?.trim()?.let { it.ifEmpty { null } }
)?.trim()?.let { it.ifEmpty { null } }.also {
if (it != null) {
log.info("Deprecation status for package $packageName found: $it")
} else {
log.warn("Deprecation status for package $packageName not found")
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@ import com.github.warningimhack3r.npmupdatedependencies.backend.data.Deprecation
import com.github.warningimhack3r.npmupdatedependencies.backend.data.ScanResult
import com.github.warningimhack3r.npmupdatedependencies.ui.statusbar.StatusBarHelper
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project

@Service(Service.Level.PROJECT)
class NUDState {
companion object {
@JvmStatic
fun getInstance(project: Project): NUDState = project.service()
}

val availableUpdates = mutableMapOf<String, ScanResult>()
val deprecations = mutableMapOf<String, Deprecation>()
val packageRegistries = mutableMapOf<String, String>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,19 @@ import com.github.warningimhack3r.npmupdatedependencies.settings.NUDSettingsStat
import com.github.warningimhack3r.npmupdatedependencies.ui.helpers.NUDHelper
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 org.semver4j.Semver

@Service(Service.Level.PROJECT)
class PackageUpdateChecker(private val project: Project) {
companion object {
private val log = logger<PackageUpdateChecker>()

@JvmStatic
fun getInstance(project: Project): PackageUpdateChecker = project.service()
}

private fun isVersionUpgradable(version: String): Boolean {
return !(version.startsWith("http")
|| version.startsWith("git")
Expand All @@ -22,7 +30,13 @@ class PackageUpdateChecker(private val project: Project) {
return comparator.split(" ").any { comp ->
val comparatorVersion = NUDHelper.Regex.semverPrefix.replace(comp, "")
if (comparatorVersion.trim().isEmpty()) return@any false
Semver.coerce(comparatorVersion)?.let { version.isGreaterThan(it) } == true
Semver.coerce(comparatorVersion)?.let { version.isGreaterThan(it) }.also {
when (it) {
true -> log.debug("Version $version is greater than $comparatorVersion")
false -> log.debug("Version $version is not greater than $comparatorVersion")
else -> log.warn("Comparator version $comparatorVersion is invalid (comparator: $comp)")
}
} == true
}
}

Expand All @@ -38,39 +52,52 @@ class PackageUpdateChecker(private val project: Project) {

private fun getVersionExcludingFilter(packageName: String, version: Semver): String? {
return NUDSettingsState.instance.excludedVersions[packageName]?.let { excludedVersions ->
log.debug("Excluded versions for $packageName: $excludedVersions")
excludedVersions.firstOrNull { excludedVersion ->
version.satisfies(excludedVersion)
version.satisfies(excludedVersion).also { satisfies ->
if (satisfies) {
log.debug("Version $version satisfies excluded version $excludedVersion")
} else {
log.debug("Version $version does not satisfy excluded version $excludedVersion")
}
}
}
}
}

fun areUpdatesAvailable(packageName: String, comparator: String): ScanResult? {
val availableUpdates = project.service<NUDState>().availableUpdates
log.info("Checking for updates for $packageName with comparator $comparator")
val availableUpdates = NUDState.getInstance(project).availableUpdates
if (!isVersionUpgradable(comparator)) {
if (availableUpdates.containsKey(packageName)) {
availableUpdates.remove(packageName)
}
log.warn("Comparator $comparator is not upgradable, removing cached versions for $packageName")
return null
}

// Check if an update has already been found
availableUpdates[packageName]?.let { cachedVersions ->
if (areVersionsMatchingComparatorNeeds(cachedVersions.versions, comparator)) {
log.info("Cached versions for $packageName are still valid, returning them")
return cachedVersions
}
}

// Check if an update is available
val npmjsClient = project.service<NPMJSClient>()
val npmjsClient = NPMJSClient.getInstance(project)
var newestVersion = npmjsClient.getLatestVersion(packageName)?.let {
Semver.coerce(it)
} ?: return null
} ?: return null.also {
log.warn("No latest version found for $packageName")
}
var satisfyingVersion: Semver? = null
val updateAvailable = isVersionMoreRecentThanComparator(newestVersion, comparator)
if (!updateAvailable) {
if (availableUpdates.containsKey(packageName)) {
availableUpdates.remove(packageName)
}
log.info("No update available for $packageName, removing cached versions")
return null
}

Expand All @@ -81,6 +108,7 @@ class PackageUpdateChecker(private val project: Project) {
|| newestVersion.build.isNotEmpty()
|| !newestVersion.satisfies(comparator)
) {
log.debug("Latest version $newestVersion is excluded, a beta, or does not satisfy the comparator")
val allVersions = npmjsClient.getAllVersions(packageName)?.mapNotNull { version ->
Semver.coerce(version)
}?.sortedDescending() ?: emptyList()
Expand All @@ -94,11 +122,14 @@ class PackageUpdateChecker(private val project: Project) {
} else if (version.preRelease.isEmpty() && version.build.isEmpty()
&& isVersionMoreRecentThanComparator(version, comparator)
) {
log.debug("Found latest version $version that satisfies the comparator, excluding filters: $filtersAffectingVersions")
latest = version
break
}
}
newestVersion = latest ?: return null // No version greater than the comparator and not filtered
newestVersion = latest ?: return null.also { // No version greater than the comparator and not filtered
log.warn("No latest version found for $packageName that satisfies the comparator")
}

// Find satisfying version
if (!newestVersion.satisfies(comparator)) {
Expand All @@ -111,13 +142,15 @@ class PackageUpdateChecker(private val project: Project) {
&& version.satisfies(comparator)
&& isVersionMoreRecentThanComparator(version, comparator)
}
log.debug("Found satisfying version $satisfyingVersion for $packageName, excluding filters: $filtersAffectingVersions")
}
}

return ScanResult(
Versions(newestVersion, satisfyingVersion),
filtersAffectingVersions
).also {
log.info("Found updates for $packageName: $it")
availableUpdates[packageName] = it
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
package com.github.warningimhack3r.npmupdatedependencies.backend.engine

import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project

@Service(Service.Level.PROJECT)
class RegistriesScanner {
companion object {
private val log = logger<RegistriesScanner>()

@JvmStatic
fun getInstance(project: Project): RegistriesScanner = project.service()
}

var registries: List<String> = emptyList()

fun scan() {
log.info("Starting to scan registries")
// Run `npm config ls` to get the list of registries
val config = ShellRunner.execute(arrayOf("npm", "config", "ls")) ?: return
registries = config.lines().asSequence().filter { line ->
Expand All @@ -24,5 +35,6 @@ class RegistriesScanner {
.replace("\"", "")
}
}.map { it.removeSuffix("/") }.distinct().toList()
log.info("Found registries: $registries")
}
}
Loading

0 comments on commit c662b9d

Please sign in to comment.