diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts new file mode 100644 index 0000000..a42a469 --- /dev/null +++ b/build-logic/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + `kotlin-dsl` + `java-gradle-plugin` +} + +repositories { + google() + mavenCentral() +} + +gradlePlugin { + plugins { + create("secretsPlugin") { + id = "secrets-plugin" + implementationClass = "SecretsPlugin" + } + } +} diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts new file mode 100644 index 0000000..7fbbd44 --- /dev/null +++ b/build-logic/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "build-logic" diff --git a/build-logic/src/main/kotlin/SecretsPlugin.kt b/build-logic/src/main/kotlin/SecretsPlugin.kt new file mode 100644 index 0000000..f2ba21d --- /dev/null +++ b/build-logic/src/main/kotlin/SecretsPlugin.kt @@ -0,0 +1,110 @@ +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.tasks.SourceSetContainer +import java.io.File +import java.util.Properties + +abstract class SecretsPlugin : Plugin { + override fun apply(project: Project) { + fun String.asRootFile() = + project.rootProject.file(this) + + val secrets = SECRETS_FILE.asRootFile() + val dir = project.layout.buildDirectory.dir(OUTPUT_DIR) + + val generateSecrets = project.tasks.register(TASK_NAME) { + outputs.dir(dir) + doLast { + if (!secrets.exists()) + error(MISSING_SECRET_PROPS_ERROR_MSG) + + val props = secrets.asProps() + if(props.useLocalMocks()) + println(USE_LOCAL_MOCKS_MSG) + + dir.get().asFile.resolve("$OUTPUT_FILE.kt").apply { + parentFile.mkdirs() + val text = with(props) { + if(useLocalMocks()) asUseLocalMocksText() else asSecretsText() + } + writeText(text) + } + } + } + + project.afterEvaluate { + project.extensions.configure("sourceSets") { + getByName("main").java.srcDir(dir) + } + tasks.matching { it.name.matches(Regex(".*[k|K](?:otlin|tlint).*")) }.configureEach { + dependsOn(generateSecrets) + } + } + + } + + companion object { + private const val SECRETS_FILE = "secrets.properties" + private const val KEY_USE_LOCAL_MOCKS = "USE_LOCAL_MOCKS" + 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 = """ + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ℹ️ INFO + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + This project's '${SECRETS_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 + Wiki page under 'Project Setup' → 'Setup Supabase CLI': + > https://github.com/PPartisan/Roky/wiki/Project-Setup#3-setup-supabase-cli + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + """.trimIndent() + + private val MISSING_SECRET_PROPS_ERROR_MSG = """ + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + ⚠️ 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. + + To build Roky, create a file named '$SECRETS_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 + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + """.trimIndent() + + private fun File.asProps() : Properties = Properties().apply { + inputStream().use { load(it) } + } + + private fun Properties.getBool(key: String, default: Boolean = true) = + getProperty(key)?.toBoolean()?:default + private fun Properties.useLocalMocks() = + getBool(KEY_USE_LOCAL_MOCKS) + + private fun Properties.asSecretsText() : String = buildString { + header() + this@asSecretsText.forEach { it.writeTo(this) } + footer() + } + + private fun Properties.asUseLocalMocksText() : String = buildString { + header() + (KEY_USE_LOCAL_MOCKS to getBool(KEY_USE_LOCAL_MOCKS)).writeTo(this) + footer() + } + + private fun Map.Entry<*, *>.writeTo(builder: StringBuilder) = + (key to value).writeTo(builder) + private fun Pair<*, *>.writeTo(builder: StringBuilder) = + builder.appendLine(""" const val $first = "$second"""") + + private fun StringBuilder.header() = + appendLine("object $OUTPUT_FILE {") + private fun StringBuilder.footer() = + appendLine("}") + } +} diff --git a/build.gradle.kts b/build.gradle.kts index 4a5006f..268c144 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,6 +6,7 @@ plugins { 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") + id("secrets-plugin") } group = "com.github.ppartisan.roky" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6249eb0..6fa0d5c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Mon Oct 07 19:40:39 BST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/secrets.properties b/secrets.properties new file mode 100644 index 0000000..920c17b --- /dev/null +++ b/secrets.properties @@ -0,0 +1,3 @@ +# Determines whether this project should use hardcoded mocks or Supabase-CLI. +# See https://github.com/PPartisan/Roky/wiki/Project-Setup +USE_LOCAL_MOCKS=true diff --git a/settings.gradle.kts b/settings.gradle.kts index 4cbda4e..dca4d5a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,5 @@ plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" } +includeBuild("build-logic") rootProject.name = "Roky" diff --git a/src/main/kotlin/Secrets.kt b/src/main/kotlin/Secrets.kt deleted file mode 100644 index 0b31545..0000000 --- a/src/main/kotlin/Secrets.kt +++ /dev/null @@ -1,25 +0,0 @@ -import java.io.File -import java.io.IOException -import java.util.Properties - -object Secrets { - private const val SECRETS_FILE = "secrets.properties" - private val props: Properties = - Properties().apply { - val file = File(System.getProperty("user.dir"), SECRETS_FILE) - if (!file.exists()) { - throw IOException("${file.name} does not exist.") - } - file.inputStream().use { - load(it) - } - } - - private fun String.property(): String = - props.getProperty(this) ?: throw IOException("Missing mandatory property: $this") - - val clientKey: String - get() = "CLIENT_API_KEY".property() - val serverUrl: String - get() = "SERVER_URL".property() -}