Skip to content

Commit

Permalink
2.2.1
Browse files Browse the repository at this point in the history
- Fix Semver comparison failing – update came in time (fixes #94)
- Fix last missing UpdateInBackground for actions
- Improve crash reporter to drop `at` lines until last "Caused by:" if any
- Use koltinx JSON parser instead of gson
- Remove useless trailing slash in registry resolution
- Upgrade dependencies
  • Loading branch information
WarningImHack3r committed Apr 26, 2024
1 parent 6d29ecc commit fc87470
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 60 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ jobs:

# Run Qodana inspections
- name: Qodana - Code Inspection
uses: JetBrains/qodana-action@v2023.3.2
uses: JetBrains/qodana-action@v2024.1.2
with:
cache-default-branch-only: true

Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@

## [Unreleased]

### Changed

- Improve crash reporter to include more relevant information

### Fixed

- Fix a library crash due to a version parsing logic problem (#94)
- Fix yet another compatibility issue with newer versions (2024.1+) (#94)

## [2.2.0] - 2024-04-17

### Added
Expand Down
8 changes: 6 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ fun environment(key: String) = providers.environmentVariable(key)
plugins {
id("java") // Java support
alias(libs.plugins.kotlin) // Kotlin support
kotlin(libs.plugins.serialization.get().pluginId) version libs.versions.kotlin // Kotlin Serialization support
alias(libs.plugins.gradleIntelliJPlugin) // Gradle IntelliJ Plugin
alias(libs.plugins.changelog) // Gradle Changelog Plugin
alias(libs.plugins.qodana) // Gradle Qodana Plugin
Expand Down Expand Up @@ -73,7 +74,7 @@ tasks {
val start = "<!-- Plugin description -->"
val end = "<!-- Plugin description end -->"

with (it.lines()) {
with(it.lines()) {
if (!containsAll(listOf(start, end))) {
throw GradleException("Plugin description section not found in README.md:\n$start ... $end")
}
Expand Down Expand Up @@ -116,6 +117,9 @@ tasks {
// The pluginVersion is based on the SemVer (https://semver.org) and supports pre-release labels, like 2.1.7-alpha.3
// Specify pre-release label to publish the plugin in a custom Release Channel automatically. Read more:
// https://plugins.jetbrains.com/docs/intellij/deployment.html#specifying-a-release-channel
channels = properties("pluginVersion").map { listOf(it.substringAfter('-', "").substringBefore('.').ifEmpty { "default" }) }
channels = properties("pluginVersion").map {
listOf(
it.substringAfter('-', "").substringBefore('.').ifEmpty { "default" })
}
}
}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pluginGroup = com.github.warningimhack3r.npmupdatedependencies
pluginName = npm-update-dependencies
pluginRepositoryUrl = https://github.com/WarningImHack3r/npm-update-dependencies
# SemVer format -> https://semver.org
pluginVersion = 2.2.0
pluginVersion = 2.2.1

# Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html
pluginSinceBuild = 221
Expand Down
5 changes: 3 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
[versions]
# libraries
annotations = "24.1.0"
semver4j = "5.2.3"
semver4j = "5.3.0"

# plugins
kotlin = "1.9.23"
changelog = "2.2.0"
gradleIntelliJPlugin = "1.17.3"
qodana = "2023.3.2"
qodana = "2024.1.2"
kover = "0.7.6"

[libraries]
Expand All @@ -18,5 +18,6 @@ semver4j = { group = "org.semver4j", name = "semver4j", version.ref = "semver4j"
changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" }
gradleIntelliJPlugin = { id = "org.jetbrains.intellij", version.ref = "gradleIntelliJPlugin" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
serialization = { id = "plugin.serialization", version.ref = "kotlin" }
kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" }
qodana = { id = "org.jetbrains.qodana", version.ref = "qodana" }
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package com.github.warningimhack3r.npmupdatedependencies.backend.engine

import com.github.warningimhack3r.npmupdatedependencies.backend.extensions.asJsonArray
import com.github.warningimhack3r.npmupdatedependencies.backend.extensions.asJsonObject
import com.github.warningimhack3r.npmupdatedependencies.backend.extensions.asString
import com.github.warningimhack3r.npmupdatedependencies.backend.extensions.parallelMap
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 kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
Expand Down Expand Up @@ -49,7 +53,7 @@ class NPMJSClient(private val project: Project) {
log.debug("No dist.tarball found for package $packageName in any registry")
}
}
val registry = "${computedRegistry.substringBefore("/$packageName")}/"
val registry = computedRegistry.substringBefore("/$packageName")
log.info("Computed registry for package $packageName: $registry")
registryForPackage[packageName] = registry
registry
Expand All @@ -76,11 +80,7 @@ class NPMJSClient(private val project: Project) {
return null
}
return try {
if (JsonParser.parseString(responseBody).isJsonObject) {
JsonParser.parseString(responseBody).asJsonObject
} else {
null
}
Json.parseToJsonElement(responseBody).jsonObject
} catch (e: Exception) {
log.warn("Error while parsing response body from $uri", e)
null
Expand Down Expand Up @@ -110,7 +110,7 @@ class NPMJSClient(private val project: Project) {
log.info("Getting all versions for package $packageName")
val registry = getRegistry(packageName)
val json = getBodyAsJSON("${registry}/$packageName")
return json?.get("versions")?.asJsonObject?.keySet()?.toList().also {
return json?.get("versions")?.asJsonObject?.keys?.toList().also {
if (it != null) {
log.info("All versions for package $packageName found in cache: $it")
}
Expand All @@ -121,7 +121,12 @@ class NPMJSClient(private val project: Project) {
log.warn("All versions for package $packageName not found")
return null
} else if (versions.startsWith("[")) {
JsonParser.parseString(versions).asJsonArray.map { it.asString }
try {
Json.parseToJsonElement(versions)
} catch (e: Exception) {
log.warn("Error while parsing all versions for package $packageName", e)
null
}?.asJsonArray?.mapNotNull { it.asString } ?: emptyList()
} else {
listOf(versions.replace("\"", ""))
}
Expand All @@ -146,7 +151,7 @@ class NPMJSClient(private val project: Project) {
if (it != null) {
log.info("Deprecation status for package $packageName found: $it")
} else {
log.warn("Deprecation status for package $packageName not found")
log.debug("No deprecation status found for package $packageName")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,28 @@ package com.github.warningimhack3r.npmupdatedependencies.backend.extensions

import com.intellij.json.psi.JsonValue
import kotlinx.coroutines.*
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive

fun <T> safeConversion(block: () -> T): T? = try {
block()
} catch (e: Exception) {
null
}

val JsonElement.asJsonObject
get() = safeConversion { jsonObject }

val JsonElement.asJsonArray
get() = safeConversion { jsonArray }

var JsonElement.asString
get() = safeConversion { jsonPrimitive.content }
set(_) {
// Do nothing
}

fun JsonValue.stringValue(): String = text.replace("\"", "")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ class GitHubErrorReportSubmitter : ErrorReportSubmitter() {
private const val MAX_URL_LENGTH = 2083
private const val BUG_LOGS_KEY = "bug-logs"
private const val TRIMMED_STACKTRACE_MARKER = "\n\n<TRIMMED STACKTRACE>"
private const val WORM_UNICODE = "\uD83D\uDC1B"
private const val UNICODE_WORM = "\uD83D\uDC1B"
}

override fun getReportActionText() = "$WORM_UNICODE Open an Issue on GitHub"
override fun getReportActionText() = "$UNICODE_WORM Open an Issue on GitHub"

override fun submit(
events: Array<out IdeaLoggingEvent>,
Expand All @@ -37,6 +37,7 @@ class GitHubErrorReportSubmitter : ErrorReportSubmitter() {
consumer: Consumer<in SubmittedReportInfo>
): Boolean {
return try {
// Base data
val event = if (events.isNotEmpty()) events.first() else null

val stackTrace = event?.throwableText ?: ""
Expand All @@ -48,13 +49,32 @@ class GitHubErrorReportSubmitter : ErrorReportSubmitter() {
DataManager.getInstance().getDataContext(parentComponent)
) ?: getLastFocusedOrOpenedProject()

// Computed data
var causedByLastIndex = -1
val splitStackTrace = stackTrace.split("\n")
splitStackTrace.reversed().forEachIndexed { index, s ->
if (s.lowercase().startsWith("caused by")) {
causedByLastIndex = splitStackTrace.size - index
return@forEachIndexed
}
}

// Build URL and content
BrowserUtil.browse(buildAbbreviatedUrl(
mapOf(
"title" to "[crash] $simpleErrorMessage",
"bug-explanation" to (additionalInfo ?: ""),
BUG_LOGS_KEY to stackTrace.split("\n").filter {
!it.trim().startsWith("at java.desktop/")
&& !it.trim().startsWith("at java.base/")
BUG_LOGS_KEY to splitStackTrace.filterIndexed { index, s ->
if (index == 0) return@filterIndexed true
val line = s.trim()
if (causedByLastIndex > 0 && line.startsWith("at ") && index < causedByLastIndex) {
return@filterIndexed false
}
!line.startsWith("at java.desktop/")
&& !line.startsWith("at java.base/")
&& !line.startsWith("at kotlin.")
&& !line.startsWith("at kotlinx.")
&& !line.startsWith("at com.intellij.")
}.joinToString("\n"),
/*"device-os" to with(System.getProperty("os.name").lowercase()) {
when { // Windows, macOS or Linux
Expand All @@ -63,7 +83,7 @@ class GitHubErrorReportSubmitter : ErrorReportSubmitter() {
else -> "Linux"
}
},*/ // currently cannot be set (https://github.com/orgs/community/discussions/44983)
"additional-device-info" to getDefaultHelpBlock(project)
"additional-device-info" to getPlatformAndPluginsInfo(project)
).filterValues { it.isNotEmpty() }
))
consumer.consume(SubmittedReportInfo(SubmittedReportInfo.SubmissionStatus.NEW_ISSUE))
Expand All @@ -74,53 +94,82 @@ class GitHubErrorReportSubmitter : ErrorReportSubmitter() {
}
}

/**
* Build the URL for the GitHub issue from the given fields, abbreviating the URL if necessary
* to fit within the maximum URL length.
*
* @param fields the fields to include in the URL.
* @return the URL for the GitHub issue.
*/
private fun buildAbbreviatedUrl(fields: Map<String, String>): URI {
val url = buildUrl(fields)
return URI(if (url.length > MAX_URL_LENGTH) {
val newMap = fields.toMutableMap()
newMap[BUG_LOGS_KEY]?.let { fullLog ->
val logLessUrlLength = buildUrl(fields.mapValues { (key, value) ->
if (key == BUG_LOGS_KEY) "" else value
}).length
val encodedLogDiff = URLEncoder.encode(fullLog, StandardCharsets.UTF_8).length - fullLog.length
newMap[BUG_LOGS_KEY] = fullLog.take(
(MAX_URL_LENGTH - logLessUrlLength - encodedLogDiff).coerceAtLeast(fullLog.substringBefore("\n").length)
).run {
if (length > fullLog.substringBefore("\n").length + TRIMMED_STACKTRACE_MARKER.length) {
"${take(length - TRIMMED_STACKTRACE_MARKER.length)}$TRIMMED_STACKTRACE_MARKER"
} else this
return URI(
if (url.length > MAX_URL_LENGTH) {
val newMap = fields.toMutableMap()
newMap[BUG_LOGS_KEY]?.let { fullLog ->
val logLessUrlLength = buildUrl(fields.mapValues { (key, value) ->
if (key == BUG_LOGS_KEY) "" else value
}).length
val encodedLogDiff = URLEncoder.encode(fullLog, StandardCharsets.UTF_8).length - fullLog.length
newMap[BUG_LOGS_KEY] = fullLog.take(
(MAX_URL_LENGTH - logLessUrlLength - encodedLogDiff).coerceAtLeast(fullLog.substringBefore("\n").length)
).run {
if (length > fullLog.substringBefore("\n").length + TRIMMED_STACKTRACE_MARKER.length) {
"${take(length - TRIMMED_STACKTRACE_MARKER.length)}$TRIMMED_STACKTRACE_MARKER"
} else this
}
}
}
val shorterLogUrl = buildUrl(newMap)
if (shorterLogUrl.length > MAX_URL_LENGTH) {
buildUrl(fields.filter { (key, _) ->
key == "title" || key == "additional-device-info"
})
} else shorterLogUrl
} else url)
val shorterLogUrl = buildUrl(newMap)
if (shorterLogUrl.length > MAX_URL_LENGTH) {
buildUrl(fields.filter { (key, _) ->
key == "title" || key == "additional-device-info"
})
} else shorterLogUrl
} else url
)
}

/**
* Build the URL for the GitHub issue from the given fields.
*
* @param fields the fields to include in the URL.
* @return the URL for the GitHub issue.
*/
private fun buildUrl(fields: Map<String, String>) = buildString {
append("https://github.com/WarningImHack3r/npm-update-dependencies/issues/new?labels=bug&template=bug_report.yml")
fields.forEach { (key, value) ->
append("&$key=${URLEncoder.encode(value, StandardCharsets.UTF_8)}")
}
}

private fun getDefaultHelpBlock(project: Project): String {
/**
* Get the platform and plugins information for the given project.
* Used in the "Additional platform info" section of the GitHub issue.
*
* @param project the [Project][com.intellij.openapi.project.Project] to get the platform and plugins information from.
* @return the platform and plugins information for the given project.
*/
private fun getPlatformAndPluginsInfo(project: Project): String {
return CompositeGeneralTroubleInfoCollector().collectInfo(project).run {
val trimmedAndCleaned = split("\n".toRegex()).filter { trim().isNotEmpty() }
// Build, JRE, JVM, OS
trimmedAndCleaned
.dropWhile { s -> s == "=== About ==="}
.takeWhile { s -> s != "=== System ===" }
.filter { s -> !s.startsWith("idea.") && !s.startsWith("Theme") }
.joinToString("\n") + "\n" +
// Plugins
trimmedAndCleaned
.dropWhile { s -> s != "=== Plugins ===" }
.takeWhile { s -> s.isNotBlank() && s.isNotEmpty() }
.joinToString("\n")
buildString {
append(
trimmedAndCleaned
.dropWhile { s -> s == "=== About ===" }
.takeWhile { s -> s != "=== System ===" }
.filter { s -> !s.startsWith("idea.") && !s.startsWith("Theme") }
.joinToString("\n")
)
append("\n")
// Plugins
append(
trimmedAndCleaned
.dropWhile { s -> s != "=== Plugins ===" }
.takeWhile { s -> s.isNotBlank() && s.isNotEmpty() }
.joinToString("\n")
)
}
}
}

Expand All @@ -131,12 +180,10 @@ class GitHubErrorReportSubmitter : ErrorReportSubmitter() {
* @return the [Project][com.intellij.openapi.project.Project] that was last in focus or open.
*/
private fun getLastFocusedOrOpenedProject(): Project {
val project = IdeFocusManager.getGlobalInstance().lastFocusedFrame?.project
if (project == null) {
return IdeFocusManager.getGlobalInstance().lastFocusedFrame?.project ?: run {
val projectManager = ProjectManager.getInstance()
val openProjects = projectManager.openProjects
return if (openProjects.isNotEmpty()) openProjects.first() else projectManager.defaultProject
if (openProjects.isNotEmpty()) openProjects.first() else projectManager.defaultProject
}
return project
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import com.github.warningimhack3r.npmupdatedependencies.ui.helpers.ActionsCommon
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.UpdateInBackground
import com.intellij.openapi.components.service

class UpdateAllSatisfiesAction : AnAction() {
class UpdateAllSatisfiesAction : AnAction(), UpdateInBackground {
override fun update(e: AnActionEvent) {
val availableUpdates = e.project?.service<NUDState>()?.availableUpdates
e.presentation.isEnabled = if (availableUpdates != null) {
Expand Down

0 comments on commit fc87470

Please sign in to comment.