feat(02-01): wire Phase 2 dependency aliases without bumping Ktor
Task 02-01-02. Adds Phase 2 deps to the version catalog and routes
them into composeApp + server build files. Ktor stays pinned at
3.4.1 per the resolved Open Question — patch bump deferred unless a
concrete incompatibility appears.
Catalog (gradle/libs.versions.toml):
- Versions: appauth, appauth-ios, androidx-security-crypto, exposed,
hikari, multiplatformSettings, testcontainers, plus the
kotlinCocoapods plugin alias.
- Libraries: ktor server auth/auth-jwt/call-logging/status-pages,
ktor client core/auth/content-negotiation/logging/okhttp/darwin/cio,
ktor-serializationKotlinxJsonMpp (the multiplatform variant; the
-jvm one stays for server), AppAuth, AndroidX Security Crypto,
multiplatform-settings + coroutines, Exposed core/jdbc/java-time,
HikariCP, Testcontainers postgresql + junit-jupiter.
composeApp/build.gradle.kts:
- Apply kotlinSerialization (alias) and kotlin.native.cocoapods (by
id — the plugin is shipped inside the Kotlin Gradle plugin already
on the classpath via recipe.kotlin.multiplatform; alias-applying
it would request a fresh version and fail).
- Cocoapods block: ComposeApp baseName + isStatic, ../iosApp/Podfile,
iOS deployment target 15.0, AppAuth pod pulled from
libs.versions.appauth.ios.get() — no literal pin in the build
file (verify-no-version-literals.sh stays green).
- Common deps: Ktor client family, MPP serialization, multiplatform
settings; Android: AppAuth-Android + Security Crypto + OkHttp
engine; iOS: Darwin engine; JVM: CIO engine.
server/build.gradle.kts: Adds Ktor server auth/JWT/CallLogging/
StatusPages, Exposed DSL trio, Hikari, kotlinx.serialization-json,
plus testImplementation testcontainers postgresql + junit-jupiter.
Deviations:
- Rule 3 (blocking): manifestPlaceholders["appAuthRedirectScheme"]
= "recipe" added to Android defaultConfig because AppAuth-Android's
bundled manifest declares a ${appAuthRedirectScheme} placeholder
that breaks AGP merge before Plan 02-04 lands the full <intent-filter>.
- Rule 3 (blocking): top-level group/version on composeApp (required
by the cocoapods podspec generator) pushes the Compose Resources
Res-class package off recipe.composeapp.generated.resources, breaking
Phase 1 App.kt imports. Lock the package via compose.resources {
packageOfResClass = "recipe.composeapp.generated.resources" }.
- Rule 3 (housekeeping): *.podspec is generated by the cocoapods
plugin on every build; ignored.
Verification:
- ./gradlew :composeApp:dependencies --configuration debugCompileClasspath
:server:dependencies --configuration runtimeClasspath: PASS
(the plan-stated androidMainCompileClasspath name doesn't exist
under this AGP/Gradle combo; debugCompileClasspath is the
functional equivalent and resolves all new deps).
- ./gradlew :composeApp:compileDebugKotlinAndroid :server:compileKotlin: PASS
- ./gradlew :composeApp:compileKotlinIosSimulatorArm64: PASS (cinterop
pulls AppAuth pod cleanly).
- ./tools/verify-no-version-literals.sh: PASS
- ./tools/verify-shared-pure.sh: PASS
- All Task 2 grep acceptance criteria satisfied.
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -17,3 +17,6 @@ captures
|
||||
!*.xcworkspace/contents.xcworkspacedata
|
||||
**/xcshareddata/WorkspaceSettings.xcsettings
|
||||
node_modules/
|
||||
|
||||
# Generated by Kotlin CocoaPods plugin (Phase 2 D-01); regenerated on every Gradle sync.
|
||||
*.podspec
|
||||
|
||||
@@ -6,9 +6,21 @@ plugins {
|
||||
alias(libs.plugins.composeMultiplatform)
|
||||
alias(libs.plugins.composeCompiler)
|
||||
alias(libs.plugins.composeHotReload)
|
||||
alias(libs.plugins.kotlinSerialization)
|
||||
// CocoaPods is shipped inside the Kotlin Gradle plugin already on the classpath via
|
||||
// `recipe.kotlin.multiplatform`. Applying via `alias(libs.plugins.kotlinCocoapods)`
|
||||
// would request a fresh version and fail with "already on the classpath", so we
|
||||
// apply it by id only. The catalog still owns the shared Kotlin version.
|
||||
id("org.jetbrains.kotlin.native.cocoapods")
|
||||
id("recipe.quality")
|
||||
}
|
||||
|
||||
// Top-level project version is required by the Kotlin CocoaPods plugin when no explicit
|
||||
// `version` is set inside the `cocoapods { ... }` block. Mirrors `server/build.gradle.kts`
|
||||
// — Gradle artifact metadata only, NOT a library/plugin pin (per `verify-no-version-literals.sh`).
|
||||
group = "dev.ulfrx.recipe"
|
||||
version = "1.0.0"
|
||||
|
||||
android {
|
||||
namespace = "dev.ulfrx.recipe"
|
||||
compileSdk = libs.versions.android.compileSdk.get().toInt()
|
||||
@@ -19,6 +31,14 @@ android {
|
||||
targetSdk = libs.versions.android.targetSdk.get().toInt()
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
|
||||
// AppAuth-Android (D-01) bundles a manifest entry for its
|
||||
// `RedirectUriReceiverActivity` that requires `${appAuthRedirectScheme}` to be
|
||||
// resolved at merge time. Pin it to the Phase 2 redirect scheme so simply
|
||||
// pulling AppAuth into the classpath (Plan 02-01) doesn't break AGP's manifest
|
||||
// merger before Plan 02-04 lands the full `<intent-filter>` registration.
|
||||
// Must match `dev.ulfrx.recipe.shared.Constants.OIDC_REDIRECT_URI` byte-for-byte.
|
||||
manifestPlaceholders["appAuthRedirectScheme"] = "recipe"
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
@@ -37,12 +57,22 @@ android {
|
||||
}
|
||||
|
||||
kotlin {
|
||||
// Create the iOS framework Swift imports as `ComposeApp`.
|
||||
listOf(iosArm64(), iosSimulatorArm64()).forEach { iosTarget ->
|
||||
iosTarget.binaries.framework {
|
||||
// The Kotlin CocoaPods plugin (D-01) configures the iOS framework on the iOS targets
|
||||
// declared by `recipe.kotlin.multiplatform`. `baseName = "ComposeApp"` / `isStatic = true`
|
||||
// keep existing Swift `import ComposeApp` working. The AppAuth iOS pod version comes
|
||||
// from the version catalog so this build file stays free of literal pins.
|
||||
cocoapods {
|
||||
summary = "Recipe Compose Multiplatform shared framework"
|
||||
homepage = "https://github.com/ulfrxdev/recipe"
|
||||
ios.deploymentTarget = "15.0"
|
||||
podfile = project.file("../iosApp/Podfile")
|
||||
framework {
|
||||
baseName = "ComposeApp"
|
||||
isStatic = true
|
||||
}
|
||||
pod("AppAuth") {
|
||||
version = libs.versions.appauth.ios.get()
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@@ -61,15 +91,45 @@ kotlin {
|
||||
implementation(libs.androidx.lifecycle.viewmodelCompose)
|
||||
implementation(libs.androidx.lifecycle.runtimeCompose)
|
||||
implementation(projects.shared)
|
||||
|
||||
// Phase 2: Ktor client + serialization + secure settings (D-13, D-16, D-17).
|
||||
// The MPP variant of `ktor-serialization-kotlinx-json` is required here; the
|
||||
// server module keeps the `-jvm` variant via `libs.ktor.serializationKotlinxJson`.
|
||||
implementation(libs.ktor.clientCore)
|
||||
implementation(libs.ktor.clientAuth)
|
||||
implementation(libs.ktor.clientContentNegotiation)
|
||||
implementation(libs.ktor.clientLogging)
|
||||
implementation(libs.ktor.serializationKotlinxJsonMpp)
|
||||
implementation(libs.kotlinx.serializationJson)
|
||||
implementation(libs.multiplatform.settings)
|
||||
implementation(libs.multiplatform.settings.coroutines)
|
||||
}
|
||||
androidMain.dependencies {
|
||||
implementation(libs.compose.uiToolingPreview)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.koin.android)
|
||||
|
||||
// Phase 2 Android: AppAuth-Android + AndroidX Security Crypto for the
|
||||
// SecureAuthStateStore actual (D-01, D-13). EncryptedSharedPreferences is
|
||||
// accepted technical debt per Open Question #1; the Keystore-backed
|
||||
// implementation can replace it without touching AuthSession.
|
||||
implementation(libs.appauth)
|
||||
implementation(libs.androidx.security.crypto)
|
||||
implementation(libs.ktor.clientOkhttp)
|
||||
}
|
||||
iosMain.dependencies {
|
||||
// Phase 2 iOS: Darwin engine for Ktor; AppAuth-iOS arrives via the
|
||||
// CocoaPods block above so the shared framework links it directly.
|
||||
implementation(libs.ktor.clientDarwin)
|
||||
}
|
||||
jvmMain.dependencies {
|
||||
implementation(compose.desktop.currentOs)
|
||||
implementation(libs.kotlinx.coroutinesSwing)
|
||||
|
||||
// Phase 2 Desktop: CIO is the JVM Ktor engine for the dev-mode auth stub
|
||||
// (D-02). The full stub lives in Plan 02-04; this just makes the engine
|
||||
// available so `composeApp:run` still compiles in Phase 2.
|
||||
implementation(libs.ktor.clientCio)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,3 +137,12 @@ kotlin {
|
||||
dependencies {
|
||||
debugImplementation(libs.compose.uiTooling)
|
||||
}
|
||||
|
||||
// Adding `group = "dev.ulfrx.recipe"` (required by the Kotlin CocoaPods plugin to render
|
||||
// the podspec) shifts the Compose Resources `Res` class package from
|
||||
// `recipe.composeapp.generated.resources` to `dev.ulfrx.recipe.composeapp.generated.resources`,
|
||||
// breaking the Phase 1 `App.kt` import. Lock the historical package so this plan's wiring
|
||||
// changes don't cascade into UI code; Plan 02-04+ replaces `App.kt`'s template body anyway.
|
||||
compose.resources {
|
||||
packageOfResClass = "recipe.composeapp.generated.resources"
|
||||
}
|
||||
|
||||
@@ -8,10 +8,15 @@ androidx-appcompat = "1.7.1"
|
||||
androidx-core = "1.18.0"
|
||||
androidx-espresso = "3.7.0"
|
||||
androidx-lifecycle = "2.10.0"
|
||||
androidx-security-crypto = "1.1.0"
|
||||
androidx-testExt = "1.3.0"
|
||||
appauth = "0.11.1"
|
||||
appauth-ios = "2.0.0"
|
||||
composeHotReload = "1.0.0"
|
||||
composeMultiplatform = "1.10.3"
|
||||
exposed = "0.55.0"
|
||||
flyway = "12.4.0"
|
||||
hikari = "6.2.1"
|
||||
junit = "4.13.2"
|
||||
kermit = "2.1.0"
|
||||
koin = "4.2.1"
|
||||
@@ -21,8 +26,10 @@ kotlinx-serialization = "1.7.3"
|
||||
ktor = "3.4.1"
|
||||
logback = "1.5.32"
|
||||
material3 = "1.10.0-alpha05"
|
||||
multiplatformSettings = "1.3.0"
|
||||
postgresql = "42.7.10"
|
||||
spotless = "8.4.0"
|
||||
testcontainers = "1.21.4"
|
||||
|
||||
[libraries]
|
||||
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
|
||||
@@ -68,6 +75,40 @@ flyway-core = { module = "org.flywaydb:flyway-core", version.ref = "flyway" }
|
||||
flyway-database-postgresql = { module = "org.flywaydb:flyway-database-postgresql", version.ref = "flyway" }
|
||||
postgresql = { module = "org.postgresql:postgresql", version.ref = "postgresql" }
|
||||
|
||||
# Phase 2 — Server: Ktor auth + JWT + call logging + status pages (D-21..D-23)
|
||||
ktor-serverAuth = { module = "io.ktor:ktor-server-auth-jvm", version.ref = "ktor" }
|
||||
ktor-serverAuthJwt = { module = "io.ktor:ktor-server-auth-jwt-jvm", version.ref = "ktor" }
|
||||
ktor-serverCallLogging = { module = "io.ktor:ktor-server-call-logging-jvm", version.ref = "ktor" }
|
||||
ktor-serverStatusPages = { module = "io.ktor:ktor-server-status-pages-jvm", version.ref = "ktor" }
|
||||
|
||||
# Phase 2 — Client: Ktor client core + auth + content-negotiation + logging + engines (D-16..D-18)
|
||||
# `ktor-serializationKotlinxJsonMpp` is the multiplatform variant (no `-jvm` classifier) for
|
||||
# commonMain consumption; the `-jvm` variant above stays available to the server module.
|
||||
ktor-clientCore = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
||||
ktor-clientAuth = { module = "io.ktor:ktor-client-auth", version.ref = "ktor" }
|
||||
ktor-clientContentNegotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
|
||||
ktor-clientLogging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
|
||||
ktor-clientOkhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
|
||||
ktor-clientDarwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
|
||||
ktor-clientCio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
|
||||
ktor-serializationKotlinxJsonMpp = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
|
||||
|
||||
# Phase 2 — Client: AppAuth + Android secure storage + multiplatform-settings (D-01, D-13, AUTH-02)
|
||||
appauth = { module = "net.openid:appauth", version.ref = "appauth" }
|
||||
androidx-security-crypto = { module = "androidx.security:security-crypto", version.ref = "androidx-security-crypto" }
|
||||
multiplatform-settings = { module = "com.russhwolf:multiplatform-settings", version.ref = "multiplatformSettings" }
|
||||
multiplatform-settings-coroutines = { module = "com.russhwolf:multiplatform-settings-coroutines", version.ref = "multiplatformSettings" }
|
||||
|
||||
# Phase 2 — Server: Exposed DSL + Hikari (D-26)
|
||||
exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "exposed" }
|
||||
exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" }
|
||||
exposed-java-time = { module = "org.jetbrains.exposed:exposed-java-time", version.ref = "exposed" }
|
||||
hikari = { module = "com.zaxxer:HikariCP", version.ref = "hikari" }
|
||||
|
||||
# Phase 2 — Server tests: Testcontainers (D-21..D-25)
|
||||
testcontainers-postgresql = { module = "org.testcontainers:postgresql", version.ref = "testcontainers" }
|
||||
testcontainers-junit-jupiter = { module = "org.testcontainers:junit-jupiter", version.ref = "testcontainers" }
|
||||
|
||||
[plugins]
|
||||
androidApplication = { id = "com.android.application", version.ref = "agp" }
|
||||
androidLibrary = { id = "com.android.library", version.ref = "agp" }
|
||||
@@ -78,5 +119,6 @@ kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
|
||||
kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||
ktor = { id = "io.ktor.plugin", version.ref = "ktor" }
|
||||
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
||||
kotlinCocoapods = { id = "org.jetbrains.kotlin.native.cocoapods", version.ref = "kotlin" }
|
||||
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
|
||||
flywayPlugin = { id = "org.flywaydb.flyway", version.ref = "flyway" }
|
||||
|
||||
@@ -29,13 +29,32 @@ dependencies {
|
||||
implementation(libs.ktor.serverNetty)
|
||||
implementation(libs.ktor.serverContentNegotiation)
|
||||
implementation(libs.ktor.serializationKotlinxJson)
|
||||
implementation(libs.kotlinx.serializationJson)
|
||||
implementation(libs.logback)
|
||||
implementation(libs.flyway.core)
|
||||
implementation(libs.flyway.database.postgresql)
|
||||
implementation(libs.postgresql)
|
||||
implementation(projects.shared)
|
||||
|
||||
// Phase 2: Ktor auth + JWT validation + observability (D-21..D-23).
|
||||
implementation(libs.ktor.serverAuth)
|
||||
implementation(libs.ktor.serverAuthJwt)
|
||||
implementation(libs.ktor.serverCallLogging)
|
||||
implementation(libs.ktor.serverStatusPages)
|
||||
|
||||
// Phase 2: Exposed DSL + Hikari connection pool (D-26).
|
||||
implementation(libs.exposed.core)
|
||||
implementation(libs.exposed.jdbc)
|
||||
implementation(libs.exposed.java.time)
|
||||
implementation(libs.hikari)
|
||||
|
||||
testImplementation(libs.ktor.serverTestHost)
|
||||
testImplementation(libs.kotlin.testJunit)
|
||||
|
||||
// Phase 2: Testcontainers for JIT user provisioning + JWT auth integration tests
|
||||
// (AUTH-03, AUTH-06). Wired here so Plan 02-02 only needs to write tests.
|
||||
testImplementation(libs.testcontainers.postgresql)
|
||||
testImplementation(libs.testcontainers.junit.jupiter)
|
||||
}
|
||||
|
||||
flyway {
|
||||
|
||||
Reference in New Issue
Block a user