From 931cf1f38b9fe96018d214bb2cec7db92810a572 Mon Sep 17 00:00:00 2001 From: easyhooon Date: Mon, 10 Aug 2026 03:22:55 +0900 Subject: [PATCH 1/8] feat: add KMP support to dari-core --- .github/workflows/kmp-core.yml | 49 +++++++++++++++ README.md | 12 +++- build.gradle.kts | 2 + dari-core/build.gradle.kts | 39 +++++++----- .../com/easyhooon/dari/Clock.android.kt | 3 + .../kotlin/com/easyhooon/dari/Clock.kt | 3 + .../kotlin/com/easyhooon/dari/DariConfig.kt | 0 .../com/easyhooon/dari/MessageDirection.kt | 0 .../kotlin/com/easyhooon/dari/MessageEntry.kt | 4 +- .../easyhooon/dari/MessagePayloadMetadata.kt | 0 .../com/easyhooon/dari/MessageStatus.kt | 2 + .../dari/interceptor/DariInterceptor.kt | 0 .../interceptor/ProtobufDariInterceptor.kt | 0 .../com/easyhooon/dari/DariConfigTest.kt | 14 ++--- .../com/easyhooon/dari/MessageEntryTest.kt | 30 +++++++++ .../dari/PublicApiCompatibilityTest.kt | 62 +++++++++++++++++++ .../kotlin/com/easyhooon/dari/Clock.ios.kt | 5 ++ .../kotlin/com/easyhooon/dari/Clock.jvm.kt | 3 + dari-noop/build.gradle.kts | 4 +- dari/build.gradle.kts | 4 +- documentation/content/docs/ko/modules.mdx | 10 ++- documentation/content/docs/modules.mdx | 10 ++- gradle/libs.versions.toml | 8 ++- 23 files changed, 229 insertions(+), 35 deletions(-) create mode 100644 .github/workflows/kmp-core.yml create mode 100644 dari-core/src/androidMain/kotlin/com/easyhooon/dari/Clock.android.kt create mode 100644 dari-core/src/commonMain/kotlin/com/easyhooon/dari/Clock.kt rename dari-core/src/{main => commonMain}/kotlin/com/easyhooon/dari/DariConfig.kt (100%) rename dari-core/src/{main => commonMain}/kotlin/com/easyhooon/dari/MessageDirection.kt (100%) rename dari-core/src/{main => commonMain}/kotlin/com/easyhooon/dari/MessageEntry.kt (96%) rename dari-core/src/{main => commonMain}/kotlin/com/easyhooon/dari/MessagePayloadMetadata.kt (100%) rename dari-core/src/{main => commonMain}/kotlin/com/easyhooon/dari/MessageStatus.kt (99%) rename dari-core/src/{main => commonMain}/kotlin/com/easyhooon/dari/interceptor/DariInterceptor.kt (100%) rename dari-core/src/{main => commonMain}/kotlin/com/easyhooon/dari/interceptor/ProtobufDariInterceptor.kt (100%) rename dari-core/src/{test => commonTest}/kotlin/com/easyhooon/dari/DariConfigTest.kt (79%) create mode 100644 dari-core/src/commonTest/kotlin/com/easyhooon/dari/MessageEntryTest.kt create mode 100644 dari-core/src/commonTest/kotlin/com/easyhooon/dari/PublicApiCompatibilityTest.kt create mode 100644 dari-core/src/iosMain/kotlin/com/easyhooon/dari/Clock.ios.kt create mode 100644 dari-core/src/jvmMain/kotlin/com/easyhooon/dari/Clock.jvm.kt diff --git a/.github/workflows/kmp-core.yml b/.github/workflows/kmp-core.yml new file mode 100644 index 0000000..d78567a --- /dev/null +++ b/.github/workflows/kmp-core.yml @@ -0,0 +1,49 @@ +name: KMP Core + +on: + pull_request: + paths: + - "dari-core/**" + - "dari/build.gradle.kts" + - "dari-noop/build.gradle.kts" + - "gradle/**" + - "build.gradle.kts" + - ".github/workflows/kmp-core.yml" + push: + branches: [main] + paths: + - "dari-core/**" + - "dari/build.gradle.kts" + - "dari-noop/build.gradle.kts" + - "gradle/**" + - "build.gradle.kts" + - ".github/workflows/kmp-core.yml" + +permissions: + contents: read + +jobs: + verify: + runs-on: macos-15 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 17 + - uses: gradle/actions/setup-gradle@v4 + - name: Verify shared contracts and Android consumers + run: >- + ./gradlew + :dari-core:jvmTest + :dari-core:testAndroidHostTest + :dari-core:compileKotlinIosX64 + :dari-core:compileKotlinIosArm64 + :dari-core:compileKotlinIosSimulatorArm64 + :dari-core:compileTestKotlinIosX64 + :dari-core:compileTestKotlinIosArm64 + :dari-core:compileTestKotlinIosSimulatorArm64 + :dari-core:ktlintCheck + :dari-core:publishToMavenLocal + :dari:assemble + :dari-noop:assemble diff --git a/README.md b/README.md index 89f939a..893a702 100644 --- a/README.md +++ b/README.md @@ -216,12 +216,22 @@ class MyApp : Application() { ``` dari/ -├── dari-core/ ← Shared types (DariConfig, MessageEntry, DariInterceptor, etc.) +├── dari-core/ ← KMP contracts for Android, JVM, and iOS ├── dari/ ← Debug library (interceptor, UI, notifications) ├── dari-noop/ ← Release no-op (same API surface, zero overhead) └── sample/ ← Sample app with WebView bridge demo ``` +`dari-core` can be added to `commonMain` in a Kotlin Multiplatform project: + +```kotlin +commonMain.dependencies { + implementation("io.github.easyhooon:dari-core:$dariVersion") +} +``` + +This shares message models and interceptor contracts across Android, JVM, and iOS targets. The inspector runtime, UI, persistence, notifications, and WebView integration in `dari` remain Android-only. + ## Sample App The `sample/` module contains a working WebView demo with realistic bridge scenarios: fetching app/device info, showing toast messages, haptic feedback, sharing via native share sheet, clipboard access, opening app settings, and requesting camera permission. Run the sample app, tap the buttons, and observe: diff --git a/build.gradle.kts b/build.gradle.kts index 89f252e..2f05e2c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,7 +5,9 @@ import org.jlleitschuh.gradle.ktlint.KtlintExtension plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.android.library) apply false + alias(libs.plugins.android.kotlin.multiplatform.library) apply false alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.kotlin.multiplatform) apply false alias(libs.plugins.kotlin.compose) apply false alias(libs.plugins.ksp) apply false alias(libs.plugins.kotlin.serialization) apply false diff --git a/dari-core/build.gradle.kts b/dari-core/build.gradle.kts index 63c64ba..3be4d05 100644 --- a/dari-core/build.gradle.kts +++ b/dari-core/build.gradle.kts @@ -1,26 +1,37 @@ -import com.vanniktech.maven.publish.SonatypeHost +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.kotlin.multiplatform.library) alias(libs.plugins.vanniktech.maven.publish) } -android { - namespace = "com.easyhooon.dari.core" - compileSdk = 36 +tasks.withType().configureEach { + compilerOptions.jvmTarget.set(JvmTarget.JVM_11) +} - defaultConfig { +kotlin { + android { + namespace = "com.easyhooon.dari.core" + compileSdk = 36 minSdk = 26 - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + withJava() + withHostTestBuilder {} } -} -dependencies { - testImplementation(libs.junit) + jvm() + + iosX64() + iosArm64() + iosSimulatorArm64() + + sourceSets { + commonTest.dependencies { + implementation(kotlin("test")) + } + } } mavenPublishing { @@ -58,6 +69,6 @@ mavenPublishing { } } - publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) + publishToMavenCentral() signAllPublications() } diff --git a/dari-core/src/androidMain/kotlin/com/easyhooon/dari/Clock.android.kt b/dari-core/src/androidMain/kotlin/com/easyhooon/dari/Clock.android.kt new file mode 100644 index 0000000..8c17c24 --- /dev/null +++ b/dari-core/src/androidMain/kotlin/com/easyhooon/dari/Clock.android.kt @@ -0,0 +1,3 @@ +package com.easyhooon.dari + +internal actual fun currentTimeMillis(): Long = System.currentTimeMillis() diff --git a/dari-core/src/commonMain/kotlin/com/easyhooon/dari/Clock.kt b/dari-core/src/commonMain/kotlin/com/easyhooon/dari/Clock.kt new file mode 100644 index 0000000..48c2eb9 --- /dev/null +++ b/dari-core/src/commonMain/kotlin/com/easyhooon/dari/Clock.kt @@ -0,0 +1,3 @@ +package com.easyhooon.dari + +internal expect fun currentTimeMillis(): Long diff --git a/dari-core/src/main/kotlin/com/easyhooon/dari/DariConfig.kt b/dari-core/src/commonMain/kotlin/com/easyhooon/dari/DariConfig.kt similarity index 100% rename from dari-core/src/main/kotlin/com/easyhooon/dari/DariConfig.kt rename to dari-core/src/commonMain/kotlin/com/easyhooon/dari/DariConfig.kt diff --git a/dari-core/src/main/kotlin/com/easyhooon/dari/MessageDirection.kt b/dari-core/src/commonMain/kotlin/com/easyhooon/dari/MessageDirection.kt similarity index 100% rename from dari-core/src/main/kotlin/com/easyhooon/dari/MessageDirection.kt rename to dari-core/src/commonMain/kotlin/com/easyhooon/dari/MessageDirection.kt diff --git a/dari-core/src/main/kotlin/com/easyhooon/dari/MessageEntry.kt b/dari-core/src/commonMain/kotlin/com/easyhooon/dari/MessageEntry.kt similarity index 96% rename from dari-core/src/main/kotlin/com/easyhooon/dari/MessageEntry.kt rename to dari-core/src/commonMain/kotlin/com/easyhooon/dari/MessageEntry.kt index fc49e3c..1d9ed98 100644 --- a/dari-core/src/main/kotlin/com/easyhooon/dari/MessageEntry.kt +++ b/dari-core/src/commonMain/kotlin/com/easyhooon/dari/MessageEntry.kt @@ -19,7 +19,7 @@ data class MessageEntry( val requestDataTruncated: Boolean = false, val responseDataTruncated: Boolean = false, val status: MessageStatus = MessageStatus.IN_PROGRESS, - val requestTimestamp: Long = System.currentTimeMillis(), + val requestTimestamp: Long = currentTimeMillis(), val responseTimestamp: Long? = null, val requestPayloadMetadata: MessagePayloadMetadata? = null, val responsePayloadMetadata: MessagePayloadMetadata? = null, @@ -109,4 +109,4 @@ data class MessageEntry( } } -private fun String?.utf8Size(): Int = this?.toByteArray(Charsets.UTF_8)?.size ?: 0 +private fun String?.utf8Size(): Int = this?.encodeToByteArray()?.size ?: 0 diff --git a/dari-core/src/main/kotlin/com/easyhooon/dari/MessagePayloadMetadata.kt b/dari-core/src/commonMain/kotlin/com/easyhooon/dari/MessagePayloadMetadata.kt similarity index 100% rename from dari-core/src/main/kotlin/com/easyhooon/dari/MessagePayloadMetadata.kt rename to dari-core/src/commonMain/kotlin/com/easyhooon/dari/MessagePayloadMetadata.kt diff --git a/dari-core/src/main/kotlin/com/easyhooon/dari/MessageStatus.kt b/dari-core/src/commonMain/kotlin/com/easyhooon/dari/MessageStatus.kt similarity index 99% rename from dari-core/src/main/kotlin/com/easyhooon/dari/MessageStatus.kt rename to dari-core/src/commonMain/kotlin/com/easyhooon/dari/MessageStatus.kt index b4cf9d4..8842572 100644 --- a/dari-core/src/main/kotlin/com/easyhooon/dari/MessageStatus.kt +++ b/dari-core/src/commonMain/kotlin/com/easyhooon/dari/MessageStatus.kt @@ -3,8 +3,10 @@ package com.easyhooon.dari enum class MessageStatus { /** Request has been sent but no response received yet (e.g., waiting for permission dialog) */ IN_PROGRESS, + /** Response received successfully */ SUCCESS, + /** Response received with an error */ ERROR, } diff --git a/dari-core/src/main/kotlin/com/easyhooon/dari/interceptor/DariInterceptor.kt b/dari-core/src/commonMain/kotlin/com/easyhooon/dari/interceptor/DariInterceptor.kt similarity index 100% rename from dari-core/src/main/kotlin/com/easyhooon/dari/interceptor/DariInterceptor.kt rename to dari-core/src/commonMain/kotlin/com/easyhooon/dari/interceptor/DariInterceptor.kt diff --git a/dari-core/src/main/kotlin/com/easyhooon/dari/interceptor/ProtobufDariInterceptor.kt b/dari-core/src/commonMain/kotlin/com/easyhooon/dari/interceptor/ProtobufDariInterceptor.kt similarity index 100% rename from dari-core/src/main/kotlin/com/easyhooon/dari/interceptor/ProtobufDariInterceptor.kt rename to dari-core/src/commonMain/kotlin/com/easyhooon/dari/interceptor/ProtobufDariInterceptor.kt diff --git a/dari-core/src/test/kotlin/com/easyhooon/dari/DariConfigTest.kt b/dari-core/src/commonTest/kotlin/com/easyhooon/dari/DariConfigTest.kt similarity index 79% rename from dari-core/src/test/kotlin/com/easyhooon/dari/DariConfigTest.kt rename to dari-core/src/commonTest/kotlin/com/easyhooon/dari/DariConfigTest.kt index 1dd12ee..806bb22 100644 --- a/dari-core/src/test/kotlin/com/easyhooon/dari/DariConfigTest.kt +++ b/dari-core/src/commonTest/kotlin/com/easyhooon/dari/DariConfigTest.kt @@ -1,9 +1,9 @@ package com.easyhooon.dari -import org.junit.Assert.assertEquals -import org.junit.Assert.assertNull -import org.junit.Assert.assertThrows -import org.junit.Test +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertNull import kotlin.time.Duration import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.hours @@ -30,14 +30,14 @@ class DariConfigTest { @Test fun `zero retention period throws`() { - assertThrows(IllegalArgumentException::class.java) { + assertFailsWith { DariConfig(retentionPeriod = Duration.ZERO) } } @Test fun `negative retention period throws`() { - assertThrows(IllegalArgumentException::class.java) { + assertFailsWith { DariConfig(retentionPeriod = (-1).seconds) } } @@ -45,7 +45,7 @@ class DariConfigTest { @Test fun `other validation still applies`() { // Sanity check that retentionPeriod was added without breaking maxContentLength validation. - assertThrows(IllegalArgumentException::class.java) { + assertFailsWith { DariConfig(maxContentLength = 0) } } diff --git a/dari-core/src/commonTest/kotlin/com/easyhooon/dari/MessageEntryTest.kt b/dari-core/src/commonTest/kotlin/com/easyhooon/dari/MessageEntryTest.kt new file mode 100644 index 0000000..df5f79e --- /dev/null +++ b/dari-core/src/commonTest/kotlin/com/easyhooon/dari/MessageEntryTest.kt @@ -0,0 +1,30 @@ +package com.easyhooon.dari + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class MessageEntryTest { + @Test + fun `default timestamp uses platform clock`() { + val before = currentTimeMillis() + val entry = MessageEntry(handlerName = "handler", direction = MessageDirection.WEB_TO_APP) + val after = currentTimeMillis() + + assertTrue(entry.requestTimestamp in before..after) + } + + @Test + fun `payload size is calculated in UTF-8 bytes`() { + val entry = MessageEntry( + handlerName = "handler", + direction = MessageDirection.APP_TO_WEB, + requestData = "Dari 다리", + responseData = "ok", + ) + + assertEquals(11, entry.requestSizeBytes) + assertEquals(2, entry.responseSizeBytes) + assertEquals(13, entry.totalSizeBytes) + } +} diff --git a/dari-core/src/commonTest/kotlin/com/easyhooon/dari/PublicApiCompatibilityTest.kt b/dari-core/src/commonTest/kotlin/com/easyhooon/dari/PublicApiCompatibilityTest.kt new file mode 100644 index 0000000..36befc3 --- /dev/null +++ b/dari-core/src/commonTest/kotlin/com/easyhooon/dari/PublicApiCompatibilityTest.kt @@ -0,0 +1,62 @@ +package com.easyhooon.dari + +import com.easyhooon.dari.interceptor.DariInterceptor +import kotlin.test.Test +import kotlin.test.assertEquals + +class PublicApiCompatibilityTest { + @Test + fun `legacy positional message constructor remains available`() { + val entry = MessageEntry( + 1L, + "request-id", + "handler", + MessageDirection.WEB_TO_APP, + "request", + "response", + MessageStatus.SUCCESS, + 100L, + 120L, + ) + + assertEquals(20L, entry.durationMs) + } + + @Test + fun `interceptor contract remains implementable from common code`() { + val interceptor: DariInterceptor = RecordingInterceptor() + + interceptor.onAppToWebRequest("handler", "request-id", "payload") + + assertEquals("handler", (interceptor as RecordingInterceptor).handlerName) + } + + private class RecordingInterceptor : DariInterceptor { + var handlerName: String? = null + + override fun onWebToAppRequest( + handlerName: String, + requestId: String?, + requestData: String?, + fireAndForget: Boolean?, + ) = Unit + + override fun onWebToAppResponse( + handlerName: String, + requestId: String?, + responseData: String?, + isSuccess: Boolean, + ) = Unit + + override fun onAppToWebRequest( + handlerName: String, + requestId: String?, + data: String?, + fireAndForget: Boolean?, + ) { + this.handlerName = handlerName + } + + override fun onAppToWebResponse(requestId: String?, isSuccess: Boolean, responseData: String?) = Unit + } +} diff --git a/dari-core/src/iosMain/kotlin/com/easyhooon/dari/Clock.ios.kt b/dari-core/src/iosMain/kotlin/com/easyhooon/dari/Clock.ios.kt new file mode 100644 index 0000000..a79e272 --- /dev/null +++ b/dari-core/src/iosMain/kotlin/com/easyhooon/dari/Clock.ios.kt @@ -0,0 +1,5 @@ +package com.easyhooon.dari + +import platform.Foundation.NSDate + +internal actual fun currentTimeMillis(): Long = (NSDate().timeIntervalSince1970 * 1_000.0).toLong() diff --git a/dari-core/src/jvmMain/kotlin/com/easyhooon/dari/Clock.jvm.kt b/dari-core/src/jvmMain/kotlin/com/easyhooon/dari/Clock.jvm.kt new file mode 100644 index 0000000..8c17c24 --- /dev/null +++ b/dari-core/src/jvmMain/kotlin/com/easyhooon/dari/Clock.jvm.kt @@ -0,0 +1,3 @@ +package com.easyhooon.dari + +internal actual fun currentTimeMillis(): Long = System.currentTimeMillis() diff --git a/dari-noop/build.gradle.kts b/dari-noop/build.gradle.kts index fac76a8..e536a3c 100644 --- a/dari-noop/build.gradle.kts +++ b/dari-noop/build.gradle.kts @@ -1,5 +1,3 @@ -import com.vanniktech.maven.publish.SonatypeHost - plugins { alias(libs.plugins.android.library) alias(libs.plugins.vanniktech.maven.publish) @@ -63,6 +61,6 @@ mavenPublishing { } } - publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) + publishToMavenCentral() signAllPublications() } diff --git a/dari/build.gradle.kts b/dari/build.gradle.kts index b40b8d6..8c7f868 100644 --- a/dari/build.gradle.kts +++ b/dari/build.gradle.kts @@ -1,5 +1,3 @@ -import com.vanniktech.maven.publish.SonatypeHost - plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.compose) @@ -111,6 +109,6 @@ mavenPublishing { } } - publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) + publishToMavenCentral() signAllPublications() } diff --git a/documentation/content/docs/ko/modules.mdx b/documentation/content/docs/ko/modules.mdx index 5174afc..931c120 100644 --- a/documentation/content/docs/ko/modules.mdx +++ b/documentation/content/docs/ko/modules.mdx @@ -7,7 +7,13 @@ Dari는 릴리즈 빌드에 디버그 구현이 포함되지 않도록 세 모 ## `dari-core` -UI, Room DB, 알림이 없는 순수 Kotlin/Android 모듈입니다. +Android, JVM, iOS를 지원하는 Kotlin Multiplatform 계약 모듈입니다. UI, Room DB, 알림이 없으며 `commonMain`에 추가할 수 있습니다. + +```kotlin +commonMain.dependencies { + implementation("io.github.easyhooon:dari-core:") +} +``` 포함 내용: - `DariConfig`: 설정 데이터 클래스 @@ -16,6 +22,8 @@ UI, Room DB, 알림이 없는 순수 Kotlin/Android 모듈입니다. - `MessageStatus`: 열거형: `IN_PROGRESS`, `SUCCESS`, `ERROR` - `MessageDirection`: 열거형: `WEB_TO_APP`, `APP_TO_WEB` +현재 KMP 지원 범위는 공통 모델과 인터셉터 계약입니다. 인스펙터 런타임, WebView 연동, 저장소, UI는 `dari` 모듈의 Android 전용 기능으로 유지됩니다. + ## `dari` `dari-core`에 의존하는 전체 디버그 구현체입니다. diff --git a/documentation/content/docs/modules.mdx b/documentation/content/docs/modules.mdx index 0411343..00202e6 100644 --- a/documentation/content/docs/modules.mdx +++ b/documentation/content/docs/modules.mdx @@ -7,7 +7,13 @@ Dari is split into three modules to keep release builds free of debug code. ## `dari-core` -Pure Kotlin/Android module with no UI, no Room database, and no notifications. +Kotlin Multiplatform contracts module for Android, JVM, and iOS. It has no UI, Room database, or notifications and can be added to `commonMain`: + +```kotlin +commonMain.dependencies { + implementation("io.github.easyhooon:dari-core:") +} +``` Contains: - `DariConfig`: configuration data class @@ -16,6 +22,8 @@ Contains: - `MessageStatus`: enum: `IN_PROGRESS`, `SUCCESS`, `ERROR` - `MessageDirection`: enum: `WEB_TO_APP`, `APP_TO_WEB` +KMP support currently covers these shared models and interception contracts. The inspector runtime, WebView integration, persistence, and UI remain Android-only in `dari`. + ## `dari` Full debug implementation. Depends on `dari-core`. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 41437d6..6d5f3cd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,15 +7,15 @@ junitVersion = "1.3.0" espressoCore = "3.7.0" lifecycleRuntimeKtx = "2.10.0" activityCompose = "1.9.3" -kotlin = "2.0.21" -ksp = "2.0.21-1.0.28" +kotlin = "2.2.10" +ksp = "2.2.10-2.0.2" composeBom = "2026.02.01" kotlinxCoroutines = "1.10.2" kotlinxSerialization = "1.8.1" androidxStartup = "1.2.0" androidxDatastore = "1.2.1" room = "2.7.1" -vanniktechMavenPublish = "0.31.0" +vanniktechMavenPublish = "0.35.0" detekt = "1.23.8" ktlintGradle = "11.6.1" ktlintSource = "0.50.0" @@ -54,7 +54,9 @@ protobuf-javalite = { group = "com.google.protobuf", name = "protobuf-javalite", [plugins] android-application = { id = "com.android.application", version.ref = "agp" } android-library = { id = "com.android.library", version.ref = "agp" } +android-kotlin-multiplatform-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } From e430a850b10ef4630ed19ef439b8b9355192ceea Mon Sep 17 00:00:00 2001 From: easyhooon Date: Mon, 10 Aug 2026 04:34:59 +0900 Subject: [PATCH 2/8] fix: run KMP checks for wrapper changes --- .github/workflows/kmp-core.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/kmp-core.yml b/.github/workflows/kmp-core.yml index d78567a..9013eb6 100644 --- a/.github/workflows/kmp-core.yml +++ b/.github/workflows/kmp-core.yml @@ -7,6 +7,7 @@ on: - "dari/build.gradle.kts" - "dari-noop/build.gradle.kts" - "gradle/**" + - "gradlew" - "build.gradle.kts" - ".github/workflows/kmp-core.yml" push: @@ -16,6 +17,7 @@ on: - "dari/build.gradle.kts" - "dari-noop/build.gradle.kts" - "gradle/**" + - "gradlew" - "build.gradle.kts" - ".github/workflows/kmp-core.yml" From 546c723913273ed0f8c72b0ddb4e65c72259b972 Mon Sep 17 00:00:00 2001 From: easyhooon Date: Mon, 10 Aug 2026 04:35:21 +0900 Subject: [PATCH 3/8] fix: disable persisted CI credentials --- .github/workflows/kmp-core.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/kmp-core.yml b/.github/workflows/kmp-core.yml index 9013eb6..e508ba4 100644 --- a/.github/workflows/kmp-core.yml +++ b/.github/workflows/kmp-core.yml @@ -29,6 +29,8 @@ jobs: runs-on: macos-15 steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - uses: actions/setup-java@v4 with: distribution: temurin From 697fc4e748dfa8b4c11c3a26b31bccbb92ce0112 Mon Sep 17 00:00:00 2001 From: easyhooon Date: Mon, 10 Aug 2026 04:35:39 +0900 Subject: [PATCH 4/8] fix: use version placeholder in KMP setup --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 893a702..e243759 100644 --- a/README.md +++ b/README.md @@ -226,7 +226,7 @@ dari/ ```kotlin commonMain.dependencies { - implementation("io.github.easyhooon:dari-core:$dariVersion") + implementation("io.github.easyhooon:dari-core:") } ``` From bdf1694797120ef1c6bf40bc0714f973fb3fb554 Mon Sep 17 00:00:00 2001 From: easyhooon Date: Mon, 10 Aug 2026 04:36:10 +0900 Subject: [PATCH 5/8] chore: update review patterns --- .claude/skills/resolve-coderabbit-review/learned-patterns.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.claude/skills/resolve-coderabbit-review/learned-patterns.md b/.claude/skills/resolve-coderabbit-review/learned-patterns.md index 8fc7345..d21c081 100644 --- a/.claude/skills/resolve-coderabbit-review/learned-patterns.md +++ b/.claude/skills/resolve-coderabbit-review/learned-patterns.md @@ -25,6 +25,9 @@ | 동적 라우트 파라미터 하드코딩 → await params로 동적 해석 | 1 | #61 | 2026-05-14 | | `` 태그 대신 Next.js `` 사용 | 1 | #61 | 2026-05-14 | | 문서 코드 샘플에 필수 import 힌트 추가 (DX 개선) | 1 | #61 | 2026-05-14 | +| CI path filter에 실행 도구 파일 누락 방지 | 1 | #88 | 2026-08-10 | +| GitHub Actions checkout credential persistence 비활성화 | 1 | #88 | 2026-08-10 | +| 문서 dependency 예시의 미정의 버전 변수 제거 | 1 | #88 | 2026-08-10 | ## 거절 패턴 (반복적으로 거절한 코멘트 유형) From 0d1dcc30b30fc4d98c3729b83484fb59b9a4d6ec Mon Sep 17 00:00:00 2001 From: easyhooon Date: Mon, 10 Aug 2026 09:42:50 +0900 Subject: [PATCH 6/8] fix: make iOS core compile in CI --- .../src/commonTest/kotlin/com/easyhooon/dari/DariConfigTest.kt | 2 +- dari-core/src/iosMain/kotlin/com/easyhooon/dari/Clock.ios.kt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dari-core/src/commonTest/kotlin/com/easyhooon/dari/DariConfigTest.kt b/dari-core/src/commonTest/kotlin/com/easyhooon/dari/DariConfigTest.kt index 806bb22..62999cf 100644 --- a/dari-core/src/commonTest/kotlin/com/easyhooon/dari/DariConfigTest.kt +++ b/dari-core/src/commonTest/kotlin/com/easyhooon/dari/DariConfigTest.kt @@ -12,7 +12,7 @@ import kotlin.time.Duration.Companion.seconds class DariConfigTest { @Test - fun `default retention period is null (disabled)`() { + fun `default retention period is null when disabled`() { assertNull(DariConfig().retentionPeriod) } diff --git a/dari-core/src/iosMain/kotlin/com/easyhooon/dari/Clock.ios.kt b/dari-core/src/iosMain/kotlin/com/easyhooon/dari/Clock.ios.kt index a79e272..817fda0 100644 --- a/dari-core/src/iosMain/kotlin/com/easyhooon/dari/Clock.ios.kt +++ b/dari-core/src/iosMain/kotlin/com/easyhooon/dari/Clock.ios.kt @@ -1,5 +1,6 @@ package com.easyhooon.dari import platform.Foundation.NSDate +import platform.Foundation.timeIntervalSince1970 internal actual fun currentTimeMillis(): Long = (NSDate().timeIntervalSince1970 * 1_000.0).toLong() From 4fe1a4c157e2a39dc6f5bebc80371cf8b1c9fff7 Mon Sep 17 00:00:00 2001 From: easyhooon Date: Mon, 10 Aug 2026 09:47:57 +0900 Subject: [PATCH 7/8] fix: skip publication signing in KMP CI --- .github/workflows/kmp-core.yml | 1 + dari-core/build.gradle.kts | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/kmp-core.yml b/.github/workflows/kmp-core.yml index e508ba4..09b56c7 100644 --- a/.github/workflows/kmp-core.yml +++ b/.github/workflows/kmp-core.yml @@ -39,6 +39,7 @@ jobs: - name: Verify shared contracts and Android consumers run: >- ./gradlew + -PskipPublicationSigning :dari-core:jvmTest :dari-core:testAndroidHostTest :dari-core:compileKotlinIosX64 diff --git a/dari-core/build.gradle.kts b/dari-core/build.gradle.kts index 3be4d05..f745443 100644 --- a/dari-core/build.gradle.kts +++ b/dari-core/build.gradle.kts @@ -1,3 +1,4 @@ +import org.gradle.plugins.signing.Sign import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -11,6 +12,12 @@ tasks.withType().configureEach { compilerOptions.jvmTarget.set(JvmTarget.JVM_11) } +tasks.withType().configureEach { + onlyIf("publication signing is enabled") { + !providers.gradleProperty("skipPublicationSigning").isPresent + } +} + kotlin { android { namespace = "com.easyhooon.dari.core" From 7999f881da54b5a818a76bd650857cd6e0f2a066 Mon Sep 17 00:00:00 2001 From: easyhooon Date: Mon, 10 Aug 2026 11:04:09 +0900 Subject: [PATCH 8/8] test: rename API compatibility smoke test --- .../{PublicApiCompatibilityTest.kt => PublicApiSmokeTest.kt} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename dari-core/src/commonTest/kotlin/com/easyhooon/dari/{PublicApiCompatibilityTest.kt => PublicApiSmokeTest.kt} (97%) diff --git a/dari-core/src/commonTest/kotlin/com/easyhooon/dari/PublicApiCompatibilityTest.kt b/dari-core/src/commonTest/kotlin/com/easyhooon/dari/PublicApiSmokeTest.kt similarity index 97% rename from dari-core/src/commonTest/kotlin/com/easyhooon/dari/PublicApiCompatibilityTest.kt rename to dari-core/src/commonTest/kotlin/com/easyhooon/dari/PublicApiSmokeTest.kt index 36befc3..ffade67 100644 --- a/dari-core/src/commonTest/kotlin/com/easyhooon/dari/PublicApiCompatibilityTest.kt +++ b/dari-core/src/commonTest/kotlin/com/easyhooon/dari/PublicApiSmokeTest.kt @@ -4,7 +4,7 @@ import com.easyhooon.dari.interceptor.DariInterceptor import kotlin.test.Test import kotlin.test.assertEquals -class PublicApiCompatibilityTest { +class PublicApiSmokeTest { @Test fun `legacy positional message constructor remains available`() { val entry = MessageEntry(