diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..5622fa2 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,15 @@ +repos: + - repo: https://github.com/gitleaks/gitleaks + rev: v8.30.0 + hooks: + - id: gitleaks + verbose: true + args: ['-c', 'config/gitleaks/.gitleaks.toml'] + - repo: local + hooks: + - id: detekt + name: Detekt + entry: bash ./gradlew detektFull -Pandroidx.baselineprofile.skipgeneration + language: system + pass_filenames: false + verbose: true diff --git a/build.gradle.kts b/build.gradle.kts index 9ad578a..02e8356 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,6 @@ +import org.jetbrains.gradle.ext.settings +import org.jetbrains.gradle.ext.taskTriggers + plugins { // this is necessary to avoid the plugins to be loaded multiple times // in each subproject's classloader @@ -10,6 +13,7 @@ plugins { alias(libs.plugins.compose.stability.analyzer) apply false alias(libs.plugins.detekt) apply false alias(libs.plugins.dokka) + alias(libs.plugins.jetbrains.idea.ext) alias(libs.plugins.kotest.multiplatform) apply false alias(libs.plugins.kotlin.jvm) apply false alias(libs.plugins.kotlin.multiplatform) apply false @@ -30,4 +34,66 @@ tasks.register("detektFull") { task.name == "detektMainAndroid" || task.name == "detektIosMainSourceSet" } }) +} + +subprojects { + pluginManager.withPlugin("org.jetbrains.dokka") { + the().apply { + dokkaSourceSets.configureEach { + enableJdkDocumentationLink.set(false) + } + } + } +} + +tasks.register("preCommitCheck") { + group = "verification" + description = "Installs pre-commit and ensures hooks are up to date." + + val rootDirectory = rootDir + + doLast { + val isWindows = System.getProperty("os.name").lowercase().contains("win") + + fun run(cmd: List): Int = ProcessBuilder(cmd) + .directory(rootDirectory) + .inheritIO() + .start() + .waitFor() + + println("Installing pre-commit package") + if (isWindows) { + run(listOf("cmd", "/c", "python -m pip install pre-commit")) + } else { + run(listOf("/opt/homebrew/bin/brew", "install", "pre-commit")) + } + + println("Autoupdate pre-commit config") + val autoupdateExit = if (isWindows) { + run(listOf("cmd", "/c", "pre-commit autoupdate")) + } else { + run(listOf("/opt/homebrew/bin/pre-commit", "autoupdate")) + } + + println("Installing pre-commit hooks") + val installExit = if (isWindows) { + run(listOf("cmd", "/c", "pre-commit install")) + } else { + run(listOf("/opt/homebrew/bin/pre-commit", "install")) + } + + if (autoupdateExit != 0 || installExit != 0) { + throw GradleException( + "\"pre-commit\" is not installed or not in PATH. Please install it before syncing." + ) + } + } +} + +idea.project.settings { + taskTriggers { + if (System.getenv("CI") == null) { + afterSync(tasks.getByName("preCommitCheck")) + } + } } \ No newline at end of file diff --git a/config/gitleaks/.gitleaks.toml b/config/gitleaks/.gitleaks.toml new file mode 100644 index 0000000..446dece --- /dev/null +++ b/config/gitleaks/.gitleaks.toml @@ -0,0 +1 @@ +title = "DevView Gitleaks configuration" diff --git a/devview-featureflip/src/commonMain/kotlin/com/worldline/devview/featureflip/components/FeatureItem.kt b/devview-featureflip/src/commonMain/kotlin/com/worldline/devview/featureflip/components/FeatureItem.kt index 3105bba..bf3e249 100644 --- a/devview-featureflip/src/commonMain/kotlin/com/worldline/devview/featureflip/components/FeatureItem.kt +++ b/devview-featureflip/src/commonMain/kotlin/com/worldline/devview/featureflip/components/FeatureItem.kt @@ -54,9 +54,6 @@ import com.worldline.devview.featureflip.preview.FeaturePreviewParameterProvider * ``` * * @param feature The feature to display. - * @param totalFeatures Total number of features in the list (used for shape calculations). - * @param index The zero-based index of this feature in the list. - * @param isLastIndex Whether this is the last item in the list. * @param onStateChange Callback invoked when the feature's state changes via user interaction. * @param modifier Modifier to apply to the card container. * diff --git a/devview-networkmock-core/src/commonMain/kotlin/com/worldline/devview/networkmock/core/model/MockConfiguration.kt b/devview-networkmock-core/src/commonMain/kotlin/com/worldline/devview/networkmock/core/model/MockConfiguration.kt index 199682a..9315988 100644 --- a/devview-networkmock-core/src/commonMain/kotlin/com/worldline/devview/networkmock/core/model/MockConfiguration.kt +++ b/devview-networkmock-core/src/commonMain/kotlin/com/worldline/devview/networkmock/core/model/MockConfiguration.kt @@ -349,7 +349,7 @@ public data class EndpointOverride( * * ## Composite string key * Use [compositeKey] to obtain the `"{groupId}-{environmentId}-{endpointId}"` string - * required by DataStore and [com.worldline.devview.networkmock.model.NetworkMockState]: + * required by DataStore and [com.worldline.devview.networkmock.core.model.NetworkMockState]: * ```kotlin * val key = EndpointKey("my-backend", "staging", "getUser").compositeKey * // "my-backend-staging-getUser" @@ -360,9 +360,9 @@ public data class EndpointOverride( * @property endpointId The [EndpointConfig.id] (e.g. `"getUser"`) * @see MockMatch * @see EndpointDescriptor - * @see com.worldline.devview.networkmock.model.NetworkMockState - * @see com.worldline.devview.networkmock.repository.MockStateRepository - * @see com.worldline.devview.networkmock.repository.MockConfigRepository + * @see com.worldline.devview.networkmock.core.model.NetworkMockState + * @see com.worldline.devview.networkmock.core.repository.MockStateRepository + * @see com.worldline.devview.networkmock.core.repository.MockConfigRepository */ @Immutable @Serializable @@ -374,7 +374,7 @@ public data class EndpointKey( /** * The canonical `"{groupId}-{environmentId}-{endpointId}"` string used as a * DataStore preference key suffix and as the key in - * [com.worldline.devview.networkmock.model.NetworkMockState.endpointStates]. + * [com.worldline.devview.networkmock.core.model.NetworkMockState.endpointStates]. */ public val compositeKey: String get() = "$groupId-$environmentId-$endpointId" @@ -418,7 +418,7 @@ public data class EndpointKey( * @see EnvironmentConfig * @see EndpointConfig * @see EndpointKey - * @see com.worldline.devview.networkmock.repository.MockConfigRepository.findMatchingMock + * @see com.worldline.devview.networkmock.core.repository.MockConfigRepository.findMatchingMock */ @Immutable public data class MockMatch(val key: EndpointKey, val config: EndpointConfig) { diff --git a/devview-networkmock-core/src/commonMain/kotlin/com/worldline/devview/networkmock/core/model/NetworkMockState.kt b/devview-networkmock-core/src/commonMain/kotlin/com/worldline/devview/networkmock/core/model/NetworkMockState.kt index 95c2e45..c0bbd44 100644 --- a/devview-networkmock-core/src/commonMain/kotlin/com/worldline/devview/networkmock/core/model/NetworkMockState.kt +++ b/devview-networkmock-core/src/commonMain/kotlin/com/worldline/devview/networkmock/core/model/NetworkMockState.kt @@ -45,7 +45,7 @@ import kotlinx.serialization.json.JsonClassDiscriminator * * ## Environment Resolution * There is no stored "active environment" selection. The environment is derived at runtime - * by matching the incoming request's hostname against the [com.worldline.devview.networkmock.model.EnvironmentConfig.url] + * by matching the incoming request's hostname against the [com.worldline.devview.networkmock.core.model.EnvironmentConfig.url] * of each environment across all API groups. This allows the app to simultaneously target * different environments for different API groups without any manual selection. * @@ -85,9 +85,9 @@ public data class NetworkMockState( * Convenience overload of [getEndpointState] that accepts three separate string * identifiers instead of an [EndpointKey]. Delegates to the [EndpointKey] overload. * - * @param groupId The [com.worldline.devview.networkmock.model.ApiGroupConfig] identifier - * @param environmentId The [com.worldline.devview.networkmock.model.EnvironmentConfig] identifier - * @param endpointId The [com.worldline.devview.networkmock.model.EndpointConfig] identifier + * @param groupId The [com.worldline.devview.networkmock.core.model.ApiGroupConfig] identifier + * @param environmentId The [com.worldline.devview.networkmock.core.model.EnvironmentConfig] identifier + * @param endpointId The [com.worldline.devview.networkmock.core.model.EndpointConfig] identifier * @return The [EndpointMockState] if configured, or `null` if not set */ public fun getEndpointState( @@ -117,9 +117,9 @@ public data class NetworkMockState( * Convenience overload of [withEndpointState] that accepts three separate string * identifiers instead of an [EndpointKey]. Delegates to the [EndpointKey] overload. * - * @param groupId The [com.worldline.devview.networkmock.model.ApiGroupConfig] identifier - * @param environmentId The [com.worldline.devview.networkmock.model.EnvironmentConfig] identifier - * @param endpointId The [com.worldline.devview.networkmock.model.EndpointConfig] identifier + * @param groupId The [com.worldline.devview.networkmock.core.model.ApiGroupConfig] identifier + * @param environmentId The [com.worldline.devview.networkmock.core.model.EnvironmentConfig] identifier + * @param endpointId The [com.worldline.devview.networkmock.core.model.EndpointConfig] identifier * @param state The new endpoint state * @return A new [NetworkMockState] with the updated endpoint state */ diff --git a/devview-networkmock-core/src/commonMain/kotlin/com/worldline/devview/networkmock/core/repository/MockConfigRepository.kt b/devview-networkmock-core/src/commonMain/kotlin/com/worldline/devview/networkmock/core/repository/MockConfigRepository.kt index 887abf4..9e34058 100644 --- a/devview-networkmock-core/src/commonMain/kotlin/com/worldline/devview/networkmock/core/repository/MockConfigRepository.kt +++ b/devview-networkmock-core/src/commonMain/kotlin/com/worldline/devview/networkmock/core/repository/MockConfigRepository.kt @@ -235,12 +235,13 @@ public class MockConfigRepository( * different environments for different API groups without any manual switching. * * ## Matching Steps - * 1. Iterate over all [com.worldline.devview.networkmock.model.ApiGroupConfig] entries - * 2. For each group, iterate over its [com.worldline.devview.networkmock.model.EnvironmentConfig] entries - * 3. Extract the hostname from [com.worldline.devview.networkmock.model.EnvironmentConfig.url] + * 1. Iterate over all [com.worldline.devview.networkmock.core.model.ApiGroupConfig] entries + * 2. For each group, iterate over its [com.worldline.devview.networkmock.core.model.EnvironmentConfig] entries + * 3. Extract the hostname from [com.worldline.devview.networkmock.core.model.EnvironmentConfig.url] * and compare against the request host (case-insensitive) * 4. On a hostname match, build the effective endpoint list for that group+environment - * via [ApiGroupConfig.effectiveEndpoints] (shared pool + overrides + additions) + * via [ApiGroupConfig.effectiveEndpoints][com.worldline.devview.networkmock.core.model.effectiveEndpoints] + * (shared pool + overrides + additions) * 5. Match the request path and method against the effective endpoint list * 6. Return a [MockMatch] carrying [MockMatch.groupId], [MockMatch.environmentId], * [MockMatch.endpointId], and the resolved [MockMatch.config] @@ -387,9 +388,9 @@ public class MockConfigRepository( * - `getUser-404-detailed.json` → Not found with detailed error body * - `getUser-500.json` → Server error * - * @param groupId The [com.worldline.devview.networkmock.model.ApiGroupConfig] identifier - * @param environmentId The [com.worldline.devview.networkmock.model.EnvironmentConfig] identifier - * @param endpointId The [com.worldline.devview.networkmock.model.EndpointConfig] identifier + * @param groupId The [com.worldline.devview.networkmock.core.model.ApiGroupConfig] identifier + * @param environmentId The [com.worldline.devview.networkmock.core.model.EnvironmentConfig] identifier + * @param endpointId The [com.worldline.devview.networkmock.core.model.EndpointConfig] identifier * @return A deduplicated, status-code-sorted list of discovered [MockResponse] objects * (may be empty if no files are found in either location) */ @@ -488,9 +489,9 @@ public class MockConfigRepository( * 1. `files/networkmocks/responses/{groupId}/{environmentId}/{endpointId}/{fileName}` ← tried first * 2. `files/networkmocks/responses/{groupId}/{endpointId}/{fileName}` ← fallback * - * @param groupId The [com.worldline.devview.networkmock.model.ApiGroupConfig] identifier - * @param environmentId The [com.worldline.devview.networkmock.model.EnvironmentConfig] identifier - * @param endpointId The [com.worldline.devview.networkmock.model.EndpointConfig] identifier + * @param groupId The [com.worldline.devview.networkmock.core.model.ApiGroupConfig] identifier + * @param environmentId The [com.worldline.devview.networkmock.core.model.EnvironmentConfig] identifier + * @param endpointId The [com.worldline.devview.networkmock.core.model.EndpointConfig] identifier * @param fileName The response filename (e.g., `"getUser-200.json"`) * @return A [MockResponse] if the file is found in either location, or `null` on error */ diff --git a/devview-networkmock-core/src/commonMain/kotlin/com/worldline/devview/networkmock/core/repository/MockStateRepository.kt b/devview-networkmock-core/src/commonMain/kotlin/com/worldline/devview/networkmock/core/repository/MockStateRepository.kt index b9c4f1a..9b8a315 100644 --- a/devview-networkmock-core/src/commonMain/kotlin/com/worldline/devview/networkmock/core/repository/MockStateRepository.kt +++ b/devview-networkmock-core/src/commonMain/kotlin/com/worldline/devview/networkmock/core/repository/MockStateRepository.kt @@ -292,9 +292,9 @@ public class MockStateRepository(private val dataStore: DataStore) * ) * ``` * - * @param groupId The [com.worldline.devview.networkmock.model.ApiGroupConfig] identifier - * @param environmentId The [com.worldline.devview.networkmock.model.EnvironmentConfig] identifier - * @param endpointId The [com.worldline.devview.networkmock.model.EndpointConfig] identifier + * @param groupId The [com.worldline.devview.networkmock.core.model.ApiGroupConfig] identifier + * @param environmentId The [com.worldline.devview.networkmock.core.model.EnvironmentConfig] identifier + * @param endpointId The [com.worldline.devview.networkmock.core.model.EndpointConfig] identifier * @param state The new endpoint mock state */ public suspend fun setEndpointMockState( diff --git a/devview-networkmock-ktor/src/commonMain/kotlin/com/worldline/devview/networkmock/ktor/plugin/NetworkMockPlugin.kt b/devview-networkmock-ktor/src/commonMain/kotlin/com/worldline/devview/networkmock/ktor/plugin/NetworkMockPlugin.kt index 36b5d45..58a116c 100644 --- a/devview-networkmock-ktor/src/commonMain/kotlin/com/worldline/devview/networkmock/ktor/plugin/NetworkMockPlugin.kt +++ b/devview-networkmock-ktor/src/commonMain/kotlin/com/worldline/devview/networkmock/ktor/plugin/NetworkMockPlugin.kt @@ -63,7 +63,7 @@ public data class NetworkMockPluginConfig(internal val config: NetworkMockConfig * * ## Default Installation * When `NetworkMock` is registered via `rememberModules`, repositories are resolved - * automatically from [com.worldline.devview.networkmock.NetworkMockInitializer]: + * automatically from [com.worldline.devview.networkmock.core.NetworkMockInitializer]: * ```kotlin * val client = HttpClient(OkHttp) { * install(NetworkMockPlugin) @@ -121,8 +121,8 @@ public data class NetworkMockPluginConfig(internal val config: NetworkMockConfig * without issues. State reads are atomic through DataStore. * * @see NetworkMockConfig - * @see com.worldline.devview.networkmock.repository.MockConfigRepository - * @see com.worldline.devview.networkmock.repository.MockStateRepository + * @see com.worldline.devview.networkmock.core.repository.MockConfigRepository + * @see com.worldline.devview.networkmock.core.repository.MockStateRepository */ public val NetworkMockPlugin: HttpClientPlugin = object : HttpClientPlugin { diff --git a/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/NetworkMock.kt b/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/NetworkMock.kt index 4c70be9..5d1efaa 100644 --- a/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/NetworkMock.kt +++ b/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/NetworkMock.kt @@ -57,8 +57,10 @@ public sealed interface NetworkMockDestination : NavKey { * DataStore instance without depending on each other. * * [initModule] passes the initialised DataStore explicitly to - * [NetworkMockInitializer.initialize], which constructs [MockStateRepository] - * and [MockConfigRepository] once for the lifetime of the process. + * [NetworkMockInitializer.initialize], which constructs + * [MockStateRepository][com.worldline.devview.networkmock.core.repository.MockStateRepository] and + * [MockConfigRepository][com.worldline.devview.networkmock.core.repository.MockConfigRepository] + * once for the lifetime of the process. * * ## Usage * ```kotlin diff --git a/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/NetworkMockEndpointPreviewBottomSheet.kt b/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/NetworkMockEndpointPreviewBottomSheet.kt index 9676c11..7201028 100644 --- a/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/NetworkMockEndpointPreviewBottomSheet.kt +++ b/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/NetworkMockEndpointPreviewBottomSheet.kt @@ -32,7 +32,8 @@ import com.worldline.devview.networkmock.preview.PreviewSheetStatePreviewParamet import kotlinx.coroutines.launch /** - * A modal bottom sheet that displays a preview of one or two [MockResponse] payloads for a + * A modal bottom sheet that displays a preview of one or two + * [MockResponse][com.worldline.devview.networkmock.core.model.MockResponse] payloads for a * network mock endpoint. * * When [previewSheetState] is [PreviewSheetState.Single], the sheet renders the single response diff --git a/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/NetworkMockEndpointScreen.kt b/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/NetworkMockEndpointScreen.kt index 4623fb5..7801aa8 100644 --- a/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/NetworkMockEndpointScreen.kt +++ b/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/NetworkMockEndpointScreen.kt @@ -66,7 +66,7 @@ import kotlinx.collections.immutable.PersistentList * Driven entirely by [viewModel], which is constructed and provided by * [NetworkMock.registerContent] inside the `entry` lambda * so that it is scoped to the navigation entry and receives the correct - * [com.worldline.devview.networkmock.model.EndpointKey]. + * [com.worldline.devview.networkmock.core.model.EndpointKey]. * * Renders three possible states from [NetworkMockEndpointViewModel.uiState]: * - [NetworkMockEndpointUiState.Loading] — shown while mock response files are being discovered diff --git a/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/model/DisplayLine.kt b/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/model/DisplayLine.kt index d284ab8..05cb630 100644 --- a/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/model/DisplayLine.kt +++ b/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/model/DisplayLine.kt @@ -3,7 +3,7 @@ package com.worldline.devview.networkmock.model import androidx.compose.runtime.Immutable /** - * A flattened display entry used by [InlineDiffContent]. + * A flattened display entry used by `InlineDiffContent`. * Long runs of [DiffLine.Unchanged] are collapsed into [DisplayLine.Collapsed]. */ @Immutable diff --git a/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/model/GroupEnvironmentUiModel.kt b/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/model/GroupEnvironmentUiModel.kt index 8041706..a53f20a 100644 --- a/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/model/GroupEnvironmentUiModel.kt +++ b/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/model/GroupEnvironmentUiModel.kt @@ -9,8 +9,8 @@ import kotlinx.collections.immutable.PersistentList * Each tab in the Network Mock screen represents one [GroupEnvironmentUiModel], * showing the resolved endpoints for that specific group and environment. * - * @property groupId The [com.worldline.devview.networkmock.model.ApiGroupConfig] identifier - * @property environmentId The [com.worldline.devview.networkmock.model.EnvironmentConfig] identifier + * @property groupId The [com.worldline.devview.networkmock.core.model.ApiGroupConfig] identifier + * @property environmentId The [com.worldline.devview.networkmock.core.model.EnvironmentConfig] identifier * @property name Human-readable display name, e.g. `"My Backend — Staging"` * @property url The base URL for this group in this environment * @property endpoints The resolved endpoints with their current mock states diff --git a/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/viewmodel/NetworkMockViewModel.kt b/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/viewmodel/NetworkMockViewModel.kt index dd44e58..464dff4 100644 --- a/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/viewmodel/NetworkMockViewModel.kt +++ b/devview-networkmock/src/commonMain/kotlin/com/worldline/devview/networkmock/viewmodel/NetworkMockViewModel.kt @@ -60,7 +60,7 @@ public class NetworkMockViewModel( * Combined UI state for the Network Mock screen. * * Combines [MockConfiguration] (loaded once from `mocks.json`), the live - * [com.worldline.devview.networkmock.model.NetworkMockState] from DataStore, the internal + * [com.worldline.devview.networkmock.core.model.NetworkMockState] from DataStore, the internal * loading state, and the discovered [EndpointDescriptor] map into a single * [NetworkMockUiState] emission. Re-emits whenever any of the four sources change. * diff --git a/devview-utils/src/commonMain/kotlin/com/worldline/devview/utils/DataStoreDelegate.kt b/devview-utils/src/commonMain/kotlin/com/worldline/devview/utils/DataStoreDelegate.kt index 098938e..e7ca869 100644 --- a/devview-utils/src/commonMain/kotlin/com/worldline/devview/utils/DataStoreDelegate.kt +++ b/devview-utils/src/commonMain/kotlin/com/worldline/devview/utils/DataStoreDelegate.kt @@ -9,7 +9,7 @@ import androidx.datastore.preferences.core.Preferences * * Each module that requires persistent storage should declare one instance of this * class. The DataStore is initialised lazily on the first call to [init], which is - * triggered automatically by [com.worldline.devview.core.rememberModules] for any + * triggered automatically by `rememberModules` for any * module implementing [RequiresDataStore]. The `null` guard inside [init] ensures * the DataStore is created exactly once per process regardless of recompositions. * @@ -43,7 +43,6 @@ import androidx.datastore.preferences.core.Preferences * ``` * * @see RequiresDataStore - * @see com.worldline.devview.core.rememberModules */ public class DataStoreDelegate { private var instance: DataStore? = null @@ -58,7 +57,7 @@ public class DataStoreDelegate { * The `null` guard ensures the DataStore is created exactly once — subsequent * calls on recomposition are no-ops. * - * This function is called automatically by [com.worldline.devview.core.rememberModules] + * This function is called automatically by `rememberModules` * for any module implementing [RequiresDataStore]. It should not be called manually. * * @param dataStoreName The filename used for the DataStore preferences file. @@ -74,7 +73,7 @@ public class DataStoreDelegate { * Returns the initialised [DataStore] instance. * * @throws IllegalStateException if [init] has not been called yet, which - * indicates the module was not registered via [com.worldline.devview.core.rememberModules]. + * indicates the module was not registered via `rememberModules`. */ public fun get(): DataStore = instance ?: error( diff --git a/docs/contributing/code-style.md b/docs/contributing/code-style.md index 7c59131..1029123 100644 --- a/docs/contributing/code-style.md +++ b/docs/contributing/code-style.md @@ -18,12 +18,22 @@ Code style guidelines for DevView. - Use descriptive names and be consistent ## Linting & Static Analysis -- Run ktlint and detekt before submitting changes -```bash -./gradlew ktlintCheck -./gradlew detekt -``` -- Fix all reported issues before opening a pull request + +Detekt (with ktlint bundled) enforces style automatically. Run the full analysis across all modules: + +=== "Windows" + + ```bat + .\gradlew.bat detektFull + ``` + +=== "macOS / Linux" + + ```bash + ./gradlew detektFull + ``` + +The pre-commit hook runs this automatically on every `git commit`, so issues are caught before they reach CI. See [Development Setup](development.md) for hook installation. ## Documentation - Use KDoc for all public classes, functions, and properties diff --git a/docs/contributing/development.md b/docs/contributing/development.md index 59dd69d..419a3bb 100644 --- a/docs/contributing/development.md +++ b/docs/contributing/development.md @@ -1,61 +1,140 @@ -# Development Setup +# Development Setup -Guide for contributing to DevView. - -> _[Placeholder: Insert screenshot or diagram of the development environment setup. Use a device frame if relevant.]_ +Guide for setting up a local environment to contribute to DevView. ## Prerequisites + - JDK 17 or higher -- Android Studio Hedgehog or newer -- Xcode 14+ (for iOS development) +- Android Studio Ladybug or newer - Git -- Kotlin Multiplatform and Compose Multiplatform plugins +- [pre-commit](https://pre-commit.com/#install) (`pip install pre-commit` or `brew install pre-commit`) + +> iOS development additionally requires Xcode 15+ and a Mac. + +## Clone and sync -## Clone Repository ```bash git clone https://github.com/worldline/devview.git cd devview ``` -## Build -```bash -./gradlew build -``` +Open the project in Android Studio and let Gradle sync complete. On first sync, the `preCommitCheck` Gradle task runs automatically and installs the pre-commit hooks into your local `.git/hooks/`. This means **the hooks are set up for you** — no manual `pre-commit install` needed. -## Run Tests -```bash -./gradlew test -``` +If you prefer the command line or need to reinstall the hooks manually: -## Code Quality ```bash -./gradlew detekt -./gradlew ktlintCheck +pre-commit install ``` -## Generate Documentation +## Pre-commit hooks + +Two hooks run on every `git commit`: + +| Hook | What it checks | +|---|---| +| **gitleaks** | Scans staged files for accidentally committed secrets | +| **detekt** | Runs the full Detekt + ktlint analysis across all modules | + +If either hook fails, the commit is blocked. Fix the reported issues and commit again. + +To run the hooks manually without committing: + ```bash -./gradlew dokkaHtml +pre-commit run --all-files ``` -## Platform-Specific Notes -### Android -- Minimum API level 26 -- Use Android Studio for best Compose support +## Build + +=== "Windows" + + ```bat + .\gradlew.bat :sample:androidApp:assembleDebug -Pandroidx.baselineprofile.skipgeneration + ``` + +=== "macOS / Linux" + + ```bash + ./gradlew :sample:androidApp:assembleDebug -Pandroidx.baselineprofile.skipgeneration + ``` + +## Run tests + +Unit and host tests (no device required): + +=== "Windows" + + ```bat + .\gradlew.bat cleanTestAndroidHostTest testAndroidHostTest -Pandroidx.baselineprofile.skipgeneration + ``` + +=== "macOS / Linux" + + ```bash + ./gradlew cleanTestAndroidHostTest testAndroidHostTest -Pandroidx.baselineprofile.skipgeneration + ``` + +Instrumented tests (requires a connected emulator or device): + +=== "Windows" + + ```bat + .\gradlew.bat connectedAndroidDeviceTest -Pandroidx.baselineprofile.skipgeneration + ``` + +=== "macOS / Linux" + + ```bash + ./gradlew connectedAndroidDeviceTest -Pandroidx.baselineprofile.skipgeneration + ``` + +## Code quality + +Run the full Detekt + ktlint analysis across all modules: + +=== "Windows" + + ```bat + .\gradlew.bat detektFull + ``` + +=== "macOS / Linux" + + ```bash + ./gradlew detektFull + ``` + +Architecture rules (Konsist): + +=== "Windows" + + ```bat + .\gradlew.bat :konsist:test + ``` + +=== "macOS / Linux" + + ```bash + ./gradlew :konsist:test + ``` + +## Generate API docs + +=== "Windows" + + ```bat + .\gradlew.bat :internal:dokka:dokkaGenerate -Pandroidx.baselineprofile.skipgeneration + ``` + +=== "macOS / Linux" -### iOS -- Minimum iOS 16.0 -- Use Xcode 15+ and latest Compose Multiplatform plugin -- Run on simulator or real device + ```bash + ./gradlew :internal:dokka:dokkaGenerate -Pandroidx.baselineprofile.skipgeneration + ``` -## Troubleshooting -- For build errors, check JDK and plugin versions -- For platform issues, verify toolchain and dependencies -- For documentation issues, ensure Dokka is installed and configured +## Next steps -## Next Steps - See [Code Style](code-style.md) for formatting and linting rules -- Review [Pull Requests](pull-requests.md) for contribution process +- Review [Pull Requests](pull-requests.md) for the contribution process --- diff --git a/gradle.properties b/gradle.properties index e62cbfb..6d08371 100644 --- a/gradle.properties +++ b/gradle.properties @@ -11,9 +11,8 @@ org.gradle.unsafe.configuration-cache-problems=warn android.nonTransitiveRClass=true android.useAndroidX=true -kotlin.mpp.androidSourceSetLayoutVersion=2 - #Kotlin +kotlin.native.ignoreDisabledTargets=true kotlin.code.style=official kotlin.daemon.jvmargs=-Xmx3072M diff --git a/gradle/build-logic/convention/src/main/kotlin/DeviceTestConventionPlugin.kt b/gradle/build-logic/convention/src/main/kotlin/DeviceTestConventionPlugin.kt index 2d9e9e9..d5308f2 100644 --- a/gradle/build-logic/convention/src/main/kotlin/DeviceTestConventionPlugin.kt +++ b/gradle/build-logic/convention/src/main/kotlin/DeviceTestConventionPlugin.kt @@ -24,11 +24,8 @@ class DeviceTestConventionPlugin : Plugin { } sourceSets { - all { - languageSettings.optIn("androidx.compose.ui.test.ExperimentalTestApi") - } - getByName("androidDeviceTest") { + languageSettings.optIn("androidx.compose.ui.test.ExperimentalTestApi") dependencies { val composeBom = libs.findLibrary("androidx.compose.bom").get() implementation(project.dependencies.platform(composeBom)) diff --git a/gradle/build-logic/convention/src/main/kotlin/com/worldline/buildlogic/convention/Kotlin.kt b/gradle/build-logic/convention/src/main/kotlin/com/worldline/buildlogic/convention/Kotlin.kt index fd3e606..6ebca0b 100644 --- a/gradle/build-logic/convention/src/main/kotlin/com/worldline/buildlogic/convention/Kotlin.kt +++ b/gradle/build-logic/convention/src/main/kotlin/com/worldline/buildlogic/convention/Kotlin.kt @@ -77,6 +77,5 @@ internal fun Project.configureKotlinCompiler() { */ val kotlinCompilerOptions = listOf( "-Xexpect-actual-classes", - "-Xcontext-parameters", "-Xcontext-sensitive-resolution" ) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3df6f06..fa5020b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -152,6 +152,7 @@ compose-screenshot = { id = "com.android.compose.screenshot", version.ref = "com compose-stability-analyzer = { id = "com.github.skydoves.compose.stability.analyzer", version.ref = "compose-stability-analyzer" } detekt = { id = "dev.detekt", version.ref = "detekt" } dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } +jetbrains-idea-ext = { id = "org.jetbrains.gradle.plugin.idea-ext", version.ref = "jetbrains-idea-ext" } kotest-multiplatform = { id = "io.kotest", version.ref = "kotest" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } diff --git a/sample/shared/build.gradle.kts b/sample/shared/build.gradle.kts index 447d9a4..6c4165c 100644 --- a/sample/shared/build.gradle.kts +++ b/sample/shared/build.gradle.kts @@ -8,6 +8,7 @@ plugins { kotlin { android { namespace = "com.worldline.devview.sample.shared" + withHostTest {} } listOf(