Skip to content

Commit

Permalink
Fetch, format and display changelog in the CLI update message (#1950)
Browse files Browse the repository at this point in the history
* Fetch, format and display changelog in the CLI update message

* Refactor changelog fetching and add unit tests
  • Loading branch information
tokou authored Sep 4, 2024
1 parent 79ee03e commit ec553d9
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 4 deletions.
16 changes: 12 additions & 4 deletions maestro-cli/src/main/java/maestro/cli/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import picocli.CommandLine.Command
import picocli.CommandLine.Option
import java.util.Properties
import kotlin.system.exitProcess
import maestro.cli.util.ChangeLogUtils

@Command(
name = "maestro",
Expand Down Expand Up @@ -147,11 +148,18 @@ fun main(args: Array<String>) {

val newVersion = Updates.checkForUpdates()
if (newVersion != null) {
Updates.fetchChangelogAsync()
System.err.println()
System.err.println(
("A new version of the Maestro CLI is available ($newVersion). Upgrade command:\n" +
"curl -Ls \"https://get.maestro.mobile.dev\" | bash").box()
)
val changelog = Updates.getChangelog()
val anchor = newVersion.toString().replace(".", "")
System.err.println(listOf(
"A new version of the Maestro CLI is available ($newVersion).\n",
"See what's new:",
"https://github.com/mobile-dev-inc/maestro/blob/main/CHANGELOG.md#$anchor",
ChangeLogUtils.print(changelog),
"Upgrade command:",
"curl -Ls \"https://get.maestro.mobile.dev\" | bash",
).joinToString("\n").box())
}

if (commandLine.isVersionHelpRequested) {
Expand Down
38 changes: 38 additions & 0 deletions maestro-cli/src/main/java/maestro/cli/update/Updates.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import maestro.cli.util.EnvUtils.CLI_VERSION
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import maestro.cli.util.ChangeLogUtils
import maestro.cli.util.ChangeLog

object Updates {
private val DEFAULT_THREAD_FACTORY = Executors.defaultThreadFactory()
Expand All @@ -15,11 +17,16 @@ object Updates {
}

private var future: CompletableFuture<CliVersion?>? = null
private var changelogFuture: CompletableFuture<List<String>>? = null

fun fetchUpdatesAsync() {
getFuture()
}

fun fetchChangelogAsync() {
getChangelogFuture()
}

fun checkForUpdates(): CliVersion? {
// Disable update check, when MAESTRO_DISABLE_UPDATE_CHECK is set to "true" e.g. when installed by a package manager. e.g. nix
if (System.getenv("MAESTRO_DISABLE_UPDATE_CHECK")?.toBoolean() == true) {
Expand All @@ -32,6 +39,18 @@ object Updates {
}
}

fun getChangelog(): List<String>? {
// Disable update check, when MAESTRO_DISABLE_UPDATE_CHECK is set to "true" e.g. when installed by a package manager. e.g. nix
if (System.getenv("MAESTRO_DISABLE_UPDATE_CHECK")?.toBoolean() == true) {
return null
}
return try {
getChangelogFuture().get(3, TimeUnit.SECONDS)
} catch (e: Exception) {
return null
}
}

private fun fetchUpdates(): CliVersion? {
if (CLI_VERSION == null) {
return null
Expand All @@ -46,6 +65,15 @@ object Updates {
}
}

private fun fetchChangelog(): ChangeLog {
if (CLI_VERSION == null) {
return null
}
val version = fetchUpdates()?.toString() ?: return null
val content = ChangeLogUtils.fetchContent()
return ChangeLogUtils.formatBody(content, version)
}

@Synchronized
private fun getFuture(): CompletableFuture<CliVersion?> {
var future = this.future
Expand All @@ -55,4 +83,14 @@ object Updates {
}
return future
}

@Synchronized
private fun getChangelogFuture(): CompletableFuture<List<String>> {
var changelogFuture = this.changelogFuture
if (changelogFuture == null) {
changelogFuture = CompletableFuture.supplyAsync(this::fetchChangelog, EXECUTOR)!!
this.changelogFuture = changelogFuture
}
return changelogFuture
}
}
27 changes: 27 additions & 0 deletions maestro-cli/src/main/java/maestro/cli/util/ChangeLogUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package maestro.cli.util

import okhttp3.OkHttpClient
import okhttp3.Request

typealias ChangeLog = List<String>?

object ChangeLogUtils {

fun formatBody(content: String?, version: String): ChangeLog = content
?.split("\n## ")?.map { it.lines() }
?.first { it.first().startsWith(version) }
?.drop(1)
?.map { it.trim().replace("**", "") }
?.map { it.replace("\\[(.*?)]\\(.*?\\)".toRegex(), "$1") }
?.filter { it.isNotEmpty() && it.startsWith("- ") }

fun fetchContent(): String? {
val request = Request.Builder()
.url("https://raw.githubusercontent.com/mobile-dev-inc/maestro/main/CHANGELOG.md")
.build()
return OkHttpClient().newCall(request).execute().body?.string()
}

fun print(changelog: ChangeLog): String =
changelog?.let { "\n${it.joinToString("\n")}\n" }.orEmpty()
}
78 changes: 78 additions & 0 deletions maestro-cli/src/test/kotlin/maestro/cli/util/ChangeLogUtilsTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package maestro.cli.util

import com.google.common.truth.Truth.assertThat
import java.io.File
import org.junit.jupiter.api.Test

class ChangeLogUtilsTest {

private val changelogFile = File(System.getProperty("user.dir"), "../CHANGELOG.md")

@Test
fun `test format last version`() {
val content = changelogFile.readText()

val changelog = ChangeLogUtils.formatBody(content, "1.38.1")

assertThat(changelog).containsExactly(
"- New commands AI visual testing: assertWithAI and assertNoDefectsWithAI",
"- Enable basic support for Maestro uploads while keeping maestro cloud functioning",
)
}

@Test
fun `test format link and no paragraph`() {
val content = changelogFile.readText()

val changelog = ChangeLogUtils.formatBody(content, "1.37.9")

assertThat(changelog).containsExactly(
"- Revert iOS landscape mode fix (#1916)",
)
}

@Test
fun `test format no subheader`() {
val content = changelogFile.readText()

val changelog = ChangeLogUtils.formatBody(content, "1.37.1")

assertThat(changelog).containsExactly(
"- Fix crash when `flutter` or `xcodebuild` is not installed (#1839)",
)
}

@Test
fun `test format strong no paragraph and no sublist`() {
val content = changelogFile.readText()

val changelog = ChangeLogUtils.formatBody(content, "1.37.0")

assertThat(changelog).containsExactly(
"- Sharding tests for parallel execution on many devices 🎉 (#1732 by Kaan)",
"- Reports in HTML (#1750 by Depa Panjie Purnama)",
"- Homebrew is back!",
"- Current platform exposed in JavaScript (#1747 by Dan Caseley)",
"- Control airplane mode (#1672 by NyCodeGHG)",
"- New `killApp` command (#1727 by Alexandre Favre)",
"- Fix cleaning up retries in iOS driver (#1669)",
"- Fix some commands not respecting custom labels (#1762 by Dan Caseley)",
"- Fix “Protocol family unavailable” when rerunning iOS tests (#1671 by Stanisław Chmiela)",
)
}

@Test
fun `test print`() {
val content = changelogFile.readText()
val changelog = ChangeLogUtils.formatBody(content, "1.17.1")

val printed = ChangeLogUtils.print(changelog)

assertThat(printed).isEqualTo(
"\n" +
"- Tweak: Remove Maestro Studio icon from Mac dock\n" +
"- Tweak: Prefer port 9999 for Maestro Studio app\n" +
"- Fix: Fix Maestro Studio conditional code snippet\n"
)
}
}

0 comments on commit ec553d9

Please sign in to comment.