Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 72 additions & 30 deletions build-logic/src/main/kotlin/SecretsPlugin.kt
Original file line number Diff line number Diff line change
@@ -1,34 +1,38 @@
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.Directory
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.SourceSetContainer
import java.io.File
import java.util.Properties

abstract class SecretsPlugin : Plugin<Project> {
override fun apply(project: Project) {
fun String.asRootFile() =
project.rootProject.file(this)
fun String.asProps(filterBy: (Map.Entry<*,*>) -> Boolean = { true }) : Properties =
project.rootProject.file(this).asProps().filter(filterBy).asProperties()

val secrets = SECRETS_FILE.asRootFile()
val dir = project.layout.buildDirectory.dir(OUTPUT_DIR)
fun String.asDir() =
project.layout.buildDirectory.dir(this)

val secrets = SECRETS_PROPERTIES_FILE.asProps()
val local = GRADLE_PROPERTIES_FILE.asProps {
it.key == KEY_USE_LOCAL_MOCKS
}
val dir = OUTPUT_DIR.asDir()

val generateSecrets = project.tasks.register(TASK_NAME) {
outputs.dir(dir)
doLast {
val useLocal = local.isTrue()
val properties = when {
useLocal -> onUseLocalMocks()
!useLocal && !secrets.isValid() -> onUseRemoteButNotSecrets()
else -> listOf(local, secrets)
}

val useLocal = GRADLE_PROPS_FILE.asRootFile().asProps().filter { it.key == KEY_USE_LOCAL_MOCKS }
if(useLocal.isTrue())
println(USE_LOCAL_MOCKS_MSG)
else if(useLocal.isFalse() && !secrets.exists())
error(MISSING_SECRET_PROPS_ERROR_MSG)

dir.get().asFile.resolve("$OUTPUT_FILE.kt").apply {
dir.createFile(OUTPUT_FILE) {
parentFile.mkdirs()
val text = listOf(useLocal, secrets.asProps())
.flatMap { it.entries }
.fold(setOf<Pair<String, String>>()) { acc, it -> acc + setOf(it.toStringPair()) }
.asConstants()
writeText(text)
writeText(properties.asText())
}
}
}
Expand All @@ -45,18 +49,21 @@ abstract class SecretsPlugin : Plugin<Project> {
}

companion object {
private const val GRADLE_PROPS_FILE = "gradle.properties"
private const val SECRETS_FILE = "secrets.properties"
private const val KEY_USE_LOCAL_MOCKS = "USE_LOCAL_MOCKS"
private const val GRADLE_PROPERTIES_FILE = "gradle.properties"
private const val SECRETS_PROPERTIES_FILE = "secrets.properties"
private const val OUTPUT_DIR = "generated/secrets"
private const val OUTPUT_FILE = "Secrets"
private const val TASK_NAME = "generateSecrets"

private val USE_LOCAL_MOCKS_MSG = """
private const val KEY_USE_LOCAL_MOCKS = "USE_LOCAL_MOCKS"
private const val KEY_SERVER_URL = "SERVER_URL"
private const val KEY_CLIENT_KEY = "CLIENT_KEY"

private val useLocalMocksMsg = """
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ℹ️ INFO
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
This project's '${SECRETS_FILE}' either does not contain the key '$KEY_USE_LOCAL_MOCKS' or its value is
This project's '${GRADLE_PROPERTIES_FILE}' either does not contain the key '$KEY_USE_LOCAL_MOCKS' or its value is
set to true.

To use a local instance of Supabase-CLI, set this property to false and follow the instructions on the Roky
Expand All @@ -65,34 +72,69 @@ abstract class SecretsPlugin : Plugin<Project> {
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
""".trimIndent()

private val MISSING_SECRET_PROPS_ERROR_MSG = """
private val missingSecretPropsErrorMsg = """
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠️ WARNING
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Your project does not contain a '$SECRETS_FILE' file. Roky requires this file to run locally as it contains
information about your local Supabase-CLI setup.
Your project either does not contain a '$SECRETS_PROPERTIES_FILE' file, or that file is empty. Roky requires this file
to run locally as it contains information about your local Supabase-CLI setup.

To build Roky, create a file named '$SECRETS_FILE' in the root directory of this project and add the values
To build Roky, create a file named '$SECRETS_PROPERTIES_FILE' in the root directory of this project and add the values
specified in the Roky Wiki page under 'Project Setup' → 'Setup Supabase CLI':
> https://github.com/PPartisan/Roky/wiki/Project-Setup#3-setup-supabase-cli

Alternatively, to use source code mocks, set 'USE_LOCAL_MOCKS=true' under 'gradle.properties'.
Your '$SECRETS_PROPERTIES_FILE' must contain these variables at a minimum:
${listOf(KEY_CLIENT_KEY, KEY_SERVER_URL)}

Alternatively, to use source code mocks, set '$KEY_USE_LOCAL_MOCKS=true' under '$GRADLE_PROPERTIES_FILE'.

This task will generate minimal default values to compile.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
""".trimIndent()

private fun Map<*,*>.asProperties() : Properties =
Properties().apply { putAll(this@asProperties) }

private fun Provider<Directory>.createFile(file: String, block: File.() -> Unit) =
get().asFile.resolve("$file.kt").apply(block)

private fun Properties.isValid() : Boolean {
if(isEmpty)
return false
return containsKey(KEY_CLIENT_KEY) && containsKey(KEY_SERVER_URL)
}

private fun onUseLocalMocks() : List<Properties> {
println(useLocalMocksMsg)
return defaults()
}

private fun onUseRemoteButNotSecrets() : List<Properties> {
println(missingSecretPropsErrorMsg)
return defaults()
}

private fun File.asProps() : Properties = Properties().apply {
if(exists())
inputStream().use { load(it) }
}

private fun defaults() : List<Properties> = Properties().apply {
this[KEY_USE_LOCAL_MOCKS] = true
listOf(KEY_SERVER_URL, KEY_CLIENT_KEY).forEach { this[it] = "" }
}.let(::listOf)

private fun Map<*,*>.isTrue() =
this[KEY_USE_LOCAL_MOCKS]?.toString()?.toBooleanStrictOrNull()?:false
private fun Map<*,*>.isFalse() =
!isTrue()

private fun Set<Pair<String,String>>.asConstants() : String = buildString {
private fun Collection<Properties>.asText() : String =
flatMap { it.entries }
.fold(setOf<Pair<String, String>>()) { acc, it -> acc + setOf(it.toStringPair()) }
.asText()

private fun Set<Pair<String,String>>.asText() : String = buildString {
header()
this@asConstants.forEach { appendLine(it.toConstant()) }
this@asText.forEach { appendLine(it.toConstant()) }
footer()
}

Expand Down
9 changes: 8 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import org.jlleitschuh.gradle.ktlint.reporter.ReporterType.*

plugins {
kotlin("jvm") version "1.9.23"
kotlin("jvm") version "2.1.0"
id("org.jetbrains.kotlinx.kover").version("0.8.3")
id("io.gitlab.arturbosch.detekt").version("1.23.3")
id("org.jlleitschuh.gradle.ktlint").version("12.1.2")
Expand Down Expand Up @@ -32,8 +32,15 @@ dependencies {
val ktorVersion = "3.0.0"
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-cio:$ktorVersion")
implementation("io.ktor:ktor-client-java:$ktorVersion")
implementation("io.ktor:ktor-client-logging:$ktorVersion")

val supabaseVersion = "3.1.1"
implementation(platform("io.github.jan-tennert.supabase:bom:$supabaseVersion"))
implementation("io.github.jan-tennert.supabase:postgrest-kt")
implementation("io.github.jan-tennert.supabase:auth-kt")
implementation("io.github.jan-tennert.supabase:realtime-kt")

implementation("org.slf4j:slf4j-simple:2.0.16")

implementation("com.googlecode.lanterna:lanterna:3.1.1")
Expand Down
14 changes: 14 additions & 0 deletions src/main/kotlin/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import com.googlecode.lanterna.gui2.MultiWindowTextGUI
import com.googlecode.lanterna.screen.Screen
import com.googlecode.lanterna.terminal.DefaultTerminalFactory
import help.helpModule
import io.github.jan.supabase.SupabaseClient
import io.github.jan.supabase.auth.Auth
import io.github.jan.supabase.createSupabaseClient
import io.github.jan.supabase.logging.LogLevel.INFO
import io.github.jan.supabase.postgrest.Postgrest
import io.github.jan.supabase.realtime.Realtime
import io.ktor.client.HttpClient
import io.ktor.client.HttpClientConfig
import io.ktor.client.engine.cio.CIO
Expand Down Expand Up @@ -54,6 +60,14 @@ val mainModules =
factoryOf(::NavigationController) binds arrayOf(NavigateToAppWindow::class, NavigateToMainMenu::class)
singleOf(::StartApp)
singleOf(::StopApp)
single<SupabaseClient> {
createSupabaseClient(Secrets.SERVER_URL, Secrets.CLIENT_KEY) {
defaultLogLevel = INFO
install(Auth)
install(Postgrest)
install(Realtime)
}
}
}

fun HttpClientConfig<*>.default() {
Expand Down