1
mirror of https://github.com/revanced/revanced-cli synced 2025-11-19 15:33:27 +01:00

Compare commits

...

18 Commits

Author SHA1 Message Date
semantic-release-bot
6306b1abfe chore(release): 5.0.0-dev.1 [skip ci]
# [5.0.0-dev.1](https://github.com/ReVanced/revanced-cli/compare/v4.6.0...v5.0.0-dev.1) (2024-08-12)

### Features

* Set patch options via CLI ([#336](https://github.com/ReVanced/revanced-cli/issues/336)) ([2300243](23002434b2))

### BREAKING CHANGES

* This commit changes various CLI options and removes the `options.json` file. Instead, patch options can now be passed via CLI options
2024-08-12 20:14:37 +00:00
oSumAtrIX
23002434b2 feat: Set patch options via CLI (#336)
BREAKING CHANGE: This commit changes various CLI options and removes the `options.json` file. Instead, patch options can now be passed via CLI options
2024-08-12 22:12:45 +02:00
oSumAtrIX
54ae01cd76 build: Bump ReVanced Patcher (#335) 2024-08-12 22:05:47 +02:00
oSumAtrIX
0a2e99c1c0 build: Remove local Maven repo and unnecessary build configuration 2024-07-26 02:03:51 +02:00
oSumAtrIX
832badef4a ci: Correct usage of repository variable 2024-07-13 00:45:05 +02:00
oSumAtrIX
40279e0369 docs: Fix invalid option name 2024-05-30 23:31:24 +02:00
oSumAtrIX
e65143851d build: Bump dependencies 2024-05-26 01:21:15 +02:00
oSumAtrIX
3bec9b5f28 docs: Improve issue templates 2024-05-26 00:43:40 +02:00
oSumAtrIX
6558aafbf1 chore: Fix spelling mistake 2024-04-05 21:17:42 +02:00
semantic-release-bot
1496e824d6 chore(release): 4.6.0 [skip ci]
# [4.6.0](https://github.com/ReVanced/revanced-cli/compare/v4.5.0...v4.6.0) (2024-04-01)

### Bug Fixes

* Copy APK to output path when it is not being signed ([366f400](366f400c5a))
* Use correct option description ([45a2ffa](45a2ffa2dd))

### Features

* Use more consistent option name ([223629c](223629c663))
2024-04-01 14:56:47 +00:00
oSumAtrIX
66a5fc2bf6 chore: Merge branch dev to main (#318) 2024-04-01 16:54:44 +02:00
oSumAtrIX
2b41fe6d0b docs: Add notice about authentication 2024-03-29 18:41:15 +01:00
semantic-release-bot
9dd460f056 chore(release): 4.6.0-dev.1 [skip ci]
# [4.6.0-dev.1](https://github.com/ReVanced/revanced-cli/compare/v4.5.1-dev.1...v4.6.0-dev.1) (2024-03-14)

### Bug Fixes

* Use correct option description ([45a2ffa](45a2ffa2dd))

### Features

* Use more consistent option name ([223629c](223629c663))
2024-03-14 13:47:05 +00:00
oSumAtrIX
223629c663 feat: Use more consistent option name 2024-03-14 13:22:54 +01:00
oSumAtrIX
45a2ffa2dd fix: Use correct option description 2024-03-14 13:22:54 +01:00
oSumAtrIX
932494eeab build: Bump dependencies 2024-03-14 13:22:54 +01:00
semantic-release-bot
0d28a49773 chore(release): 4.5.1-dev.1 [skip ci]
## [4.5.1-dev.1](https://github.com/ReVanced/revanced-cli/compare/v4.5.0...v4.5.1-dev.1) (2024-03-12)

### Bug Fixes

* Copy APK to output path when it is not being signed ([366f400](366f400c5a))
2024-03-12 13:49:51 +00:00
oSumAtrIX
366f400c5a fix: Copy APK to output path when it is not being signed
When `--mount` is used, signing is skipped. For that reason the APK was never copied to the output path.
2024-03-12 14:47:45 +01:00
26 changed files with 2851 additions and 1286 deletions

View File

@@ -70,7 +70,7 @@ body:
Before creating a new bug report, please keep the following in mind:
- **Do not submit a duplicate bug report**: You can review existing bug reports [here](https://github.com/ReVanced/revanced-cli/labels/Bug%20report).
- **Do not submit a duplicate bug report**: Search for existing bug reports [here](https://github.com/ReVanced/revanced-cli/issues?q=label%3A%22Bug+report%22).
- **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-cli/blob/main/CONTRIBUTING.md).
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
- type: textarea
@@ -102,7 +102,7 @@ body:
label: Acknowledgements
description: Your bug report will be closed if you don't follow the checklist below.
options:
- label: This issue is not a duplicate of an existing bug report.
- label: I have checked all open and closed bug reports and this is not a duplicate.
required: true
- label: I have chosen an appropriate title.
required: true

View File

@@ -70,8 +70,8 @@ body:
Before creating a new feature request, please keep the following in mind:
- **Do not submit a duplicate feature request**: You can review existing feature requests [here](https://github.com/ReVanced/revanced-cli//labels/Feature%20request).
- **Review the contribution guidelines**: Make sure your bug report adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-cli//blob/main/CONTRIBUTING.md).
- **Do not submit a duplicate feature request**: Search for existing feature requests [here](https://github.com/ReVanced/revanced-cli/issues?q=label%3A%22Feature+request%22).
- **Review the contribution guidelines**: Make sure your feature request adheres to it. You can find the guidelines [here](https://github.com/ReVanced/revanced-cli//blob/main/CONTRIBUTING.md).
- **Do not use the issue page for support**: If you need help or have questions, check out other platforms on [revanced.app](https://revanced.app).
- type: textarea
attributes:
@@ -98,7 +98,7 @@ body:
label: Acknowledgements
description: Your feature request will be closed if you don't follow the checklist below.
options:
- label: This issue is not a duplicate of an existing feature request.
- label: I have checked all open and closed feature requests and this is not a duplicate.
required: true
- label: I have chosen an appropriate title.
required: true

View File

@@ -42,7 +42,7 @@ jobs:
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.GPG_PASSPHRASE }}
fingerprint: ${{ env.GPG_FINGERPRINT }}
fingerprint: ${{ vars.GPG_FINGERPRINT }}
- name: Release
env:

2
.gitignore vendored
View File

@@ -121,5 +121,5 @@ node_modules/
revanced-cache/
options.toml
# Generated by an Android project (such as ReVanced Integrations)
# Generated by Android projects
local.properties

View File

@@ -1,3 +1,47 @@
# [5.0.0-dev.1](https://github.com/ReVanced/revanced-cli/compare/v4.6.0...v5.0.0-dev.1) (2024-08-12)
### Features
* Set patch options via CLI ([#336](https://github.com/ReVanced/revanced-cli/issues/336)) ([2300243](https://github.com/ReVanced/revanced-cli/commit/23002434b2d51c2a3b30b33dd0526261432d90ce))
### BREAKING CHANGES
* This commit changes various CLI options and removes the `options.json` file. Instead, patch options can now be passed via CLI options
# [4.6.0](https://github.com/ReVanced/revanced-cli/compare/v4.5.0...v4.6.0) (2024-04-01)
### Bug Fixes
* Copy APK to output path when it is not being signed ([366f400](https://github.com/ReVanced/revanced-cli/commit/366f400c5a46491f3f262c7ff4b0df1ae3721f74))
* Use correct option description ([45a2ffa](https://github.com/ReVanced/revanced-cli/commit/45a2ffa2dd95ee8ac3c4d466463c9a5b869b8da1))
### Features
* Use more consistent option name ([223629c](https://github.com/ReVanced/revanced-cli/commit/223629c663dcd94d237110e09e4e152aa03867f9))
# [4.6.0-dev.1](https://github.com/ReVanced/revanced-cli/compare/v4.5.1-dev.1...v4.6.0-dev.1) (2024-03-14)
### Bug Fixes
* Use correct option description ([45a2ffa](https://github.com/ReVanced/revanced-cli/commit/45a2ffa2dd95ee8ac3c4d466463c9a5b869b8da1))
### Features
* Use more consistent option name ([223629c](https://github.com/ReVanced/revanced-cli/commit/223629c663dcd94d237110e09e4e152aa03867f9))
## [4.5.1-dev.1](https://github.com/ReVanced/revanced-cli/compare/v4.5.0...v4.5.1-dev.1) (2024-03-12)
### Bug Fixes
* Copy APK to output path when it is not being signed ([366f400](https://github.com/ReVanced/revanced-cli/commit/366f400c5a46491f3f262c7ff4b0df1ae3721f74))
# [4.5.0](https://github.com/ReVanced/revanced-cli/compare/v4.4.2...v4.5.0) (2024-03-11)

View File

@@ -16,10 +16,9 @@ application {
repositories {
mavenCentral()
mavenLocal()
google()
maven {
// A repository must be speficied for some reason. "registry" is a dummy.
// A repository must be specified for some reason. "registry" is a dummy.
url = uri("https://maven.pkg.github.com/revanced/registry")
credentials {
username = project.findProperty("gpr.user") as String? ?: System.getenv("GITHUB_ACTOR")

File diff suppressed because it is too large Load Diff

View File

@@ -23,4 +23,15 @@ To build ReVanced CLI, follow these steps:
./gradlew build
```
> [!NOTE]
> If the build fails due to authentication, you may need to authenticate to GitHub Packages.
> Create a PAT with the scope `read:packages` [here](https://github.com/settings/tokens/new?scopes=read:packages&description=ReVanced) and add your token to ~/.gradle/gradle.properties.
>
> Example `gradle.properties` file:
>
> ```properties
> gpr.user = user
> gpr.key = key
> ```
After the build succeeds, the built JAR file will be located at `build/libs/revanced-cli-<version>-all.jar`.

View File

@@ -1,4 +1,4 @@
org.gradle.parallel = true
org.gradle.caching = true
kotlin.code.style = official
version = 4.5.0
version = 5.0.0-dev.1

View File

@@ -1,18 +1,18 @@
[versions]
shadow = "8.1.1"
kotlin = "1.9.22"
kotlinx-coroutines-core = "1.7.3"
picocli = "4.7.5"
revanced-patcher = "19.3.1"
revanced-library = "2.2.1"
kotlin = "2.0.0"
kotlinx = "1.8.1"
picocli = "4.7.6"
revanced-patcher = "20.0.0"
revanced-library = "3.0.0"
[libraries]
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-core" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx" }
picocli = { module = "info.picocli:picocli", version.ref = "picocli" }
revanced-patcher = { module = "app.revanced:revanced-patcher", version.ref = "revanced-patcher" }
revanced-library = { module = "app.revanced:revanced-library", version.ref = "revanced-library" }
revanced-library = { module = "app.revanced:revanced-library-jvm", version.ref = "revanced-library" }
[plugins]
shadow = { id = "com.github.johnrengelman.shadow", version.ref = "shadow" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }

Binary file not shown.

View File

@@ -1,6 +1,8 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
distributionSha256Sum=9631d53cf3e74bfa726893aee1f8994fee4e060c401335946dba2156f440f24c
distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dist
zipStorePath=wrapper/dists

17
gradlew vendored
View File

@@ -83,7 +83,8 @@ done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
@@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
@@ -201,11 +202,11 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \

20
gradlew.bat vendored
View File

@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail

3059
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,6 @@
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"gradle-semantic-release-plugin": "^1.9.1",
"semantic-release": "^23.0.2"
"semantic-release": "^23.0.8"
}
}

View File

@@ -1,7 +1 @@
rootProject.name = "revanced-cli"
buildCache {
local {
isEnabled = "CI" !in System.getenv()
}
}

View File

@@ -0,0 +1,105 @@
package app.revanced.cli.command
import picocli.CommandLine
class OptionKeyConverter : CommandLine.ITypeConverter<String> {
override fun convert(value: String): String = value
}
class OptionValueConverter : CommandLine.ITypeConverter<Any?> {
override fun convert(value: String?): Any? {
value ?: return null
return when {
value.startsWith("[") && value.endsWith("]") -> {
val innerValue = value.substring(1, value.length - 1)
buildList {
var nestLevel = 0
var insideQuote = false
var escaped = false
val item = buildString {
for (char in innerValue) {
when (char) {
'\\' -> {
if (escaped || nestLevel != 0) {
append(char)
}
escaped = !escaped
}
'"', '\'' -> {
if (!escaped) {
insideQuote = !insideQuote
} else {
escaped = false
}
append(char)
}
'[' -> {
if (!insideQuote) {
nestLevel++
}
append(char)
}
']' -> {
if (!insideQuote) {
nestLevel--
if (nestLevel == -1) {
return value
}
}
append(char)
}
',' -> if (nestLevel == 0) {
if (insideQuote) {
append(char)
} else {
add(convert(toString()))
setLength(0)
}
} else {
append(char)
}
else -> append(char)
}
}
}
if (item.isNotEmpty()) {
add(convert(item))
}
}
}
value.startsWith("\"") && value.endsWith("\"") -> value.substring(1, value.length - 1)
value.startsWith("'") && value.endsWith("'") -> value.substring(1, value.length - 1)
value.endsWith("f") -> value.dropLast(1).toFloat()
value.endsWith("L") -> value.dropLast(1).toLong()
value.equals("true", ignoreCase = true) -> true
value.equals("false", ignoreCase = true) -> false
value.toIntOrNull() != null -> value.toInt()
value.toLongOrNull() != null -> value.toLong()
value.toDoubleOrNull() != null -> value.toDouble()
value.toFloatOrNull() != null -> value.toFloat()
value == "null" -> null
value == "int[]" -> emptyList<Int>()
value == "long[]" -> emptyList<Long>()
value == "double[]" -> emptyList<Double>()
value == "float[]" -> emptyList<Float>()
value == "boolean[]" -> emptyList<Boolean>()
value == "string[]" -> emptyList<String>()
else -> value
}
}
}

View File

@@ -1,9 +1,9 @@
package app.revanced.cli.command
import app.revanced.library.PackageName
import app.revanced.library.PatchUtils
import app.revanced.library.VersionMap
import app.revanced.patcher.PatchBundleLoader
import app.revanced.library.mostCommonCompatibleVersions
import app.revanced.patcher.patch.loadPatchesFromJar
import picocli.CommandLine
import java.io.File
import java.util.logging.Logger
@@ -16,13 +16,13 @@ import java.util.logging.Logger
],
)
internal class ListCompatibleVersions : Runnable {
private val logger = Logger.getLogger(ListCompatibleVersions::class.java.name)
private val logger = Logger.getLogger(this::class.java.name)
@CommandLine.Parameters(
description = ["Paths to patch bundles."],
arity = "1..*",
)
private lateinit var patchBundles: Array<File>
private lateinit var patchBundles: Set<File>
@CommandLine.Option(
names = ["-f", "--filter-package-names"],
@@ -38,8 +38,6 @@ internal class ListCompatibleVersions : Runnable {
private var countUnusedPatches: Boolean = false
override fun run() {
val patches = PatchBundleLoader.Jar(*patchBundles)
fun VersionMap.buildVersionsString(): String {
if (isEmpty()) return "Any"
@@ -58,8 +56,9 @@ internal class ListCompatibleVersions : Runnable {
appendLine(versions.buildVersionsString().prependIndent("\t"))
}
PatchUtils.getMostCommonCompatibleVersions(
patches,
val patches = loadPatchesFromJar(patchBundles)
patches.mostCommonCompatibleVersions(
packageNames,
countUnusedPatches,
).entries.joinToString("\n", transform = ::buildString).let(logger::info)

View File

@@ -1,25 +1,26 @@
package app.revanced.cli.command
import app.revanced.patcher.PatchBundleLoader
import app.revanced.patcher.patch.Package
import app.revanced.patcher.patch.Patch
import app.revanced.patcher.patch.options.PatchOption
import app.revanced.patcher.patch.loadPatchesFromJar
import picocli.CommandLine.*
import picocli.CommandLine.Help.Visibility.ALWAYS
import java.io.File
import java.util.logging.Logger
import app.revanced.patcher.patch.Option as PatchOption
@Command(
name = "list-patches",
description = ["List patches from supplied patch bundles."],
)
internal object ListPatchesCommand : Runnable {
private val logger = Logger.getLogger(ListPatchesCommand::class.java.name)
private val logger = Logger.getLogger(this::class.java.name)
@Parameters(
description = ["Paths to patch bundles."],
arity = "1..*",
)
private lateinit var patchBundles: Array<File>
private lateinit var patchBundles: Set<File>
@Option(
names = ["-d", "--with-descriptions"],
@@ -70,16 +71,19 @@ internal object ListPatchesCommand : Runnable {
private var packageName: String? = null
override fun run() {
fun Patch.CompatiblePackage.buildString() =
buildString {
fun Package.buildString(): String {
val (name, versions) = this
return buildString {
if (withVersions && versions != null) {
appendLine("Package name: $name")
appendLine("Compatible versions:")
append(versions!!.joinToString("\n") { version -> version }.prependIndent("\t"))
append(versions.joinToString("\n") { version -> version }.prependIndent("\t"))
} else {
append("Package name: $name")
}
}
}
fun PatchOption<*>.buildString() =
buildString {
@@ -91,9 +95,11 @@ internal object ListPatchesCommand : Runnable {
} ?: append("Key: $key")
values?.let { values ->
appendLine("\nValid values:")
appendLine("\nPossible values:")
append(values.map { "${it.value} (${it.key})" }.joinToString("\n").prependIndent("\t"))
}
append("\nType: $type")
}
fun IndexedValue<Patch<*>>.buildString() =
@@ -126,10 +132,10 @@ internal object ListPatchesCommand : Runnable {
}
fun Patch<*>.filterCompatiblePackages(name: String) =
compatiblePackages?.any { it.name == name }
compatiblePackages?.any { (compatiblePackageName, _) -> compatiblePackageName == name }
?: withUniversalPatches
val patches = PatchBundleLoader.Jar(*patchBundles).withIndex().toList()
val patches = loadPatchesFromJar(patchBundles).withIndex().toList()
val filtered =
packageName?.let { patches.filter { (_, patch) -> patch.filterCompatiblePackages(it) } } ?: patches

View File

@@ -34,7 +34,6 @@ private object CLIVersionProvider : IVersionProvider {
versionProvider = CLIVersionProvider::class,
subcommands = [
PatchCommand::class,
OptionsCommand::class,
ListPatchesCommand::class,
ListCompatibleVersions::class,
UtilityCommand::class,

View File

@@ -1,62 +0,0 @@
package app.revanced.cli.command
import app.revanced.library.Options
import app.revanced.library.Options.setOptions
import app.revanced.patcher.PatchBundleLoader
import picocli.CommandLine
import picocli.CommandLine.Help.Visibility.ALWAYS
import java.io.File
import java.util.logging.Logger
@CommandLine.Command(
name = "options",
description = ["Generate options file from patches."],
)
internal object OptionsCommand : Runnable {
private val logger = Logger.getLogger(OptionsCommand::class.java.name)
@CommandLine.Parameters(
description = ["Paths to patch bundles."],
arity = "1..*",
)
private lateinit var patchBundles: Array<File>
@CommandLine.Option(
names = ["-p", "--path"],
description = ["Path to patch options JSON file."],
showDefaultValue = ALWAYS,
)
private var filePath: File = File("options.json")
@CommandLine.Option(
names = ["-o", "--overwrite"],
description = ["Overwrite existing options file."],
showDefaultValue = ALWAYS,
)
private var overwrite: Boolean = false
@CommandLine.Option(
names = ["-u", "--update"],
description = ["Update existing options by adding missing and removing non-existent options."],
showDefaultValue = ALWAYS,
)
private var update: Boolean = false
override fun run() =
try {
PatchBundleLoader.Jar(*patchBundles).let { patches ->
val exists = filePath.exists()
if (!exists || overwrite) {
if (exists && update) patches.setOptions(filePath)
Options.serialize(patches, prettyPrint = true).let(filePath::writeText)
} else {
throw OptionsFileAlreadyExistsException()
}
}
} catch (ex: OptionsFileAlreadyExistsException) {
logger.severe("Options file already exists, use --overwrite to override it")
}
class OptionsFileAlreadyExistsException : Exception()
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,9 @@
package app.revanced.cli.command.utility
import app.revanced.library.adb.AdbManager
import app.revanced.library.installation.installer.*
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import picocli.CommandLine.*
import java.io.File
import java.util.logging.Logger
@@ -10,7 +13,7 @@ import java.util.logging.Logger
description = ["Install an APK file to devices with the supplied ADB device serials"],
)
internal object InstallCommand : Runnable {
private val logger = Logger.getLogger(InstallCommand::class.java.name)
private val logger = Logger.getLogger(this::class.java.name)
@Parameters(
description = ["ADB device serials. If not supplied, the first connected device will be used."],
@@ -32,13 +35,29 @@ internal object InstallCommand : Runnable {
private var packageName: String? = null
override fun run() {
fun install(deviceSerial: String? = null) =
try {
AdbManager.getAdbManager(deviceSerial, packageName != null).install(AdbManager.Apk(apk, packageName))
} catch (e: AdbManager.DeviceNotFoundException) {
suspend fun install(deviceSerial: String? = null) {
val result = try {
if (packageName != null) {
AdbRootInstaller(deviceSerial)
} else {
AdbInstaller(deviceSerial)
}.install(Installer.Apk(apk, packageName))
} catch (e: Exception) {
logger.severe(e.toString())
}
deviceSerials?.forEach(::install) ?: install()
when (result) {
RootInstallerResult.FAILURE ->
logger.severe("Failed to mount the APK file")
is AdbInstallerResult.Failure ->
logger.severe(result.exception.toString())
else ->
logger.info("Installed the APK file")
}
}
runBlocking {
deviceSerials?.map { async { install(it) } }?.awaitAll() ?: install()
}
}
}

View File

@@ -1,6 +1,12 @@
package app.revanced.cli.command.utility
import app.revanced.library.adb.AdbManager
import app.revanced.library.installation.installer.AdbInstaller
import app.revanced.library.installation.installer.AdbInstallerResult
import app.revanced.library.installation.installer.AdbRootInstaller
import app.revanced.library.installation.installer.RootInstallerResult
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import picocli.CommandLine.*
import picocli.CommandLine.Help.Visibility.ALWAYS
import java.util.logging.Logger
@@ -10,7 +16,7 @@ import java.util.logging.Logger
description = ["Uninstall a patched app from the devices with the supplied ADB device serials"],
)
internal object UninstallCommand : Runnable {
private val logger = Logger.getLogger(UninstallCommand::class.java.name)
private val logger = Logger.getLogger(this::class.java.name)
@Parameters(
description = ["ADB device serials. If not supplied, the first connected device will be used."],
@@ -33,13 +39,28 @@ internal object UninstallCommand : Runnable {
private var unmount: Boolean = false
override fun run() {
fun uninstall(deviceSerial: String? = null) =
try {
AdbManager.getAdbManager(deviceSerial, unmount).uninstall(packageName)
} catch (e: AdbManager.DeviceNotFoundException) {
suspend fun uninstall(deviceSerial: String? = null) {
val result = try {
if (unmount) {
AdbRootInstaller(deviceSerial)
} else {
AdbInstaller(deviceSerial)
}.uninstall(packageName)
} catch (e: Exception) {
logger.severe(e.toString())
}
deviceSerials?.forEach { uninstall(it) } ?: uninstall()
when (result) {
RootInstallerResult.FAILURE ->
logger.severe("Failed to unmount the patched APK file")
is AdbInstallerResult.Failure ->
logger.severe(result.exception.toString())
else -> logger.info("Uninstalled the patched APK file")
}
}
runBlocking {
deviceSerials?.map { async { uninstall(it) } }?.awaitAll() ?: uninstall()
}
}
}

View File

@@ -0,0 +1,78 @@
package app.revanced.cli.command
import kotlin.test.Test
class OptionValueConverterTest {
@Test
fun `converts to string`() {
"string" convertsTo "string" because "Strings should remain the same"
}
@Test
fun `converts to null`() {
"null" convertsTo null because "null should convert to null"
"\"null\"" convertsTo "null" because "Escaped null should convert to a string"
}
@Test
fun `converts to boolean`() {
"true" convertsTo true because "true should convert to a boolean true"
"True" convertsTo true because "Casing should not matter"
"\"true\"" convertsTo "true" because "Escaped booleans should be converted to strings"
"\'True\'" convertsTo "True" because "Casing in escaped booleans should not matter"
"tr ue" convertsTo "tr ue" because "Malformed booleans should be converted to strings"
}
@Test
fun `converts to numbers`() {
"1" convertsTo 1 because "Integers should convert to integers"
"1.0" convertsTo 1.0 because "Doubles should convert to doubles"
"1.0f" convertsTo 1.0f because "The suffix f should convert to a float"
Long.MAX_VALUE.toString() convertsTo Long.MAX_VALUE because "Values that are too large for an integer should convert to longs"
"1L" convertsTo 1L because "The suffix L should convert to a long"
}
@Test
fun `converts escaped numbers to string`() {
"\"1\"" convertsTo "1" because "Escaped numbers should convert to strings"
"\"1.0\"" convertsTo "1.0" because "Escaped doubles should convert to strings"
"\"1L\"" convertsTo "1L" because "Escaped longs should convert to strings"
"\'1\'" convertsTo "1" because "Single quotes should not be treated as escape symbols"
"\'.0\'" convertsTo ".0" because "Single quotes should not be treated as escape symbols"
"\'1L\'" convertsTo "1L" because "Single quotes should not be treated as escape symbols"
}
@Test
fun `trims escape symbols once`() {
"\"\"\"1\"\"\"" convertsTo "\"\"1\"\"" because "The escape symbols should be trimmed once"
"\'\'\'1\'\'\'" convertsTo "''1''" because "Single quotes should not be treated as escape symbols"
}
@Test
fun `converts lists`() {
"1,2" convertsTo "1,2" because "Lists without square brackets should not be converted to lists"
"[1,2" convertsTo "[1,2" because "Invalid lists should not be converted to lists"
"\"[1,2]\"" convertsTo "[1,2]" because "Lists with escaped square brackets should not be converted to lists"
"[]" convertsTo emptyList<Any>() because "Empty untyped lists should convert to empty lists of any"
"int[]" convertsTo emptyList<Int>() because "Empty typed lists should convert to lists of the specified type"
"[[int[]]]" convertsTo listOf(listOf(emptyList<Int>())) because "Nested typed lists should convert to nested lists of the specified type"
"[\"int[]\"]" convertsTo listOf("int[]") because "Lists of escaped empty typed lists should not be converted to lists"
"[1,2,3]" convertsTo listOf(1, 2, 3) because "Lists of integers should convert to lists of integers"
"[[1]]" convertsTo listOf(listOf(1)) because "Nested lists with one element should convert to nested lists"
"[[1,2],[3,4]]" convertsTo listOf(listOf(1, 2), listOf(3, 4)) because "Nested lists should convert to nested lists"
"[\"1,2\"]" convertsTo listOf("1,2") because "Values in lists should not be split by commas in strings"
"[[\"1,2\"]]" convertsTo listOf(listOf("1,2")) because "Values in nested lists should not be split by commas in strings"
"[\"\\\"\"]" convertsTo listOf("\"") because "Escaped quotes in strings should be converted to quotes"
"[[\"\\\"\"]]" convertsTo listOf(listOf("\"")) because "Escaped quotes in strings nested in lists should be converted to quotes"
"[.1,.2f,,true,FALSE]" convertsTo listOf(.1, .2f, "", true, false) because "Values in lists should be converted to the correct type"
}
private val convert = OptionValueConverter()::convert
private infix fun String.convertsTo(to: Any?) = convert(this) to to
private infix fun Pair<Any?, Any?>.because(reason: String) = assert(this.first == this.second) { reason }
}