From c8bea6b1a07e05d0cc946f22a9a708df9b6738ff Mon Sep 17 00:00:00 2001 From: Aleksei Tokarev Date: Tue, 18 Aug 2026 15:45:52 +0300 Subject: [PATCH 1/3] ci: strengthen quality checks --- .github/dependabot.yml | 10 +++ .github/workflows/api.yml | 44 +++++++++++ .github/workflows/backend.yml | 23 ++++-- .github/workflows/branch-name.yml | 7 +- .github/workflows/docs.yml | 18 ++--- .github/workflows/frontend.yml | 75 ++++++++++++++++--- .github/workflows/markdown.yml | 14 +++- Makefile | 27 ++++--- api/openapi.yaml | 5 ++ frontend/build.gradle.kts | 25 ++++++- frontend/composeApp/build.gradle.kts | 12 +-- .../org/nznyx/choresapp/MainActivity.kt | 2 +- .../kotlin/org/nznyx/choresapp/App.kt | 20 +++-- .../nznyx/choresapp/ComposeAppCommonTest.kt | 3 +- .../org/nznyx/choresapp/MainViewController.kt | 3 +- .../kotlin/org/nznyx/choresapp/Main.kt | 14 ++++ .../kotlin/org/nznyx/choresapp/main.kt | 13 ---- .../org/nznyx/choresapp/{main.kt => Main.kt} | 2 +- frontend/detekt-config.yml | 9 +-- frontend/shared/build.gradle.kts | 10 +-- .../org/nznyx/choresapp/Platform.android.kt | 2 +- .../kotlin/org/nznyx/choresapp/Constants.kt | 2 +- .../kotlin/org/nznyx/choresapp/Greeting.kt | 2 +- .../kotlin/org/nznyx/choresapp/Platform.kt | 2 +- .../org/nznyx/choresapp/SharedCommonTest.kt | 3 +- .../org/nznyx/choresapp/Platform.ios.kt | 4 +- .../kotlin/org/nznyx/choresapp/Platform.js.kt | 4 +- .../org/nznyx/choresapp/Platform.jvm.kt | 4 +- .../org/nznyx/choresapp/Platform.wasmJs.kt | 4 +- package-lock.json | 40 +++++++--- package.json | 5 +- rust-toolchain.toml | 4 + 32 files changed, 301 insertions(+), 111 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/api.yml create mode 100644 frontend/composeApp/src/jvmMain/kotlin/org/nznyx/choresapp/Main.kt delete mode 100644 frontend/composeApp/src/jvmMain/kotlin/org/nznyx/choresapp/main.kt rename frontend/composeApp/src/webMain/kotlin/org/nznyx/choresapp/{main.kt => Main.kt} (99%) create mode 100644 rust-toolchain.toml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..6c5049e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,10 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + groups: + github-actions: + patterns: + - "*" diff --git a/.github/workflows/api.yml b/.github/workflows/api.yml new file mode 100644 index 0000000..173e071 --- /dev/null +++ b/.github/workflows/api.yml @@ -0,0 +1,44 @@ +name: API contract + +on: + push: + branches: [main] + paths: + - "api/**" + - "package.json" + - "package-lock.json" + - "Makefile" + - ".github/workflows/api.yml" + pull_request: + paths: + - "api/**" + - "package.json" + - "package-lock.json" + - "Makefile" + - ".github/workflows/api.yml" + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + openapi-contract: + name: OpenAPI contract + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Check out repository + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + - name: Set up Node.js and npm cache + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 + with: + node-version: 24 + cache: npm + - name: Install repository tooling + run: npm ci + - name: Validate OpenAPI contract + run: make lint-api diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml index 343e15b..a5b3708 100644 --- a/.github/workflows/backend.yml +++ b/.github/workflows/backend.yml @@ -7,20 +7,29 @@ on: - "backend/**" - "api/**" - "Makefile" + - "rust-toolchain.toml" - ".github/workflows/backend.yml" pull_request: +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: rust-changes: name: Rust backend change detection runs-on: ubuntu-latest + timeout-minutes: 5 outputs: relevant: ${{ steps.detect.outputs.relevant }} steps: - name: Check out repository history if: github.event_name == 'pull_request' - uses: actions/checkout@v5 + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 with: fetch-depth: 0 - name: Detect relevant changes @@ -29,7 +38,7 @@ jobs: EVENT_NAME: ${{ github.event_name }} BASE_SHA: ${{ github.event.pull_request.base.sha }} run: | - if [[ "$EVENT_NAME" == "push" ]] || ! git diff --quiet "$BASE_SHA" HEAD -- backend api Makefile .github/workflows/backend.yml; then + if [[ "$EVENT_NAME" == "push" ]] || ! git diff --quiet "$BASE_SHA" HEAD -- backend api Makefile rust-toolchain.toml .github/workflows/backend.yml; then echo "relevant=true" >> "$GITHUB_OUTPUT" else echo "relevant=false" >> "$GITHUB_OUTPUT" @@ -40,10 +49,11 @@ jobs: needs: rust-changes if: needs.rust-changes.outputs.relevant == 'true' runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Check out repository - uses: actions/checkout@v5 + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - name: Check Rust formatting run: make check-rust-format @@ -52,10 +62,11 @@ jobs: needs: rust-changes if: needs.rust-changes.outputs.relevant == 'true' runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Check out repository - uses: actions/checkout@v5 + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - name: Run Clippy with warnings denied run: make lint-rust @@ -64,10 +75,11 @@ jobs: needs: rust-changes if: needs.rust-changes.outputs.relevant == 'true' runs-on: ubuntu-latest + timeout-minutes: 15 steps: - name: Check out repository - uses: actions/checkout@v5 + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - name: Run Rust tests run: make test-backend @@ -76,6 +88,7 @@ jobs: needs: [rust-changes, rust-format, rust-clippy, rust-tests] if: always() runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Verify Rust backend jobs diff --git a/.github/workflows/branch-name.yml b/.github/workflows/branch-name.yml index 221d647..20fb008 100644 --- a/.github/workflows/branch-name.yml +++ b/.github/workflows/branch-name.yml @@ -6,17 +6,22 @@ on: permissions: {} +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: branch-name-convention: name: Branch name convention runs-on: ubuntu-latest + timeout-minutes: 5 env: BRANCH_NAME: ${{ github.head_ref }} steps: - name: Require type/lowercase-kebab-case run: | - if [[ ! "$BRANCH_NAME" =~ ^(feat|fix|chore|docs|refactor|test|ci)/[a-z0-9]+(-[a-z0-9]+)*$ ]]; then + if [[ ! "$BRANCH_NAME" =~ ^((feat|fix|chore|docs|refactor|test|ci)/[a-z0-9]+(-[a-z0-9]+)*|dependabot/github_actions/[a-zA-Z0-9._/-]+)$ ]]; then echo "::error title=Invalid branch name::$BRANCH_NAME must match type/lowercase-kebab-case" exit 1 fi diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 4ccad5b..b960c53 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,11 +9,9 @@ on: - "Makefile" - ".github/workflows/docs.yml" pull_request: - paths: - - "docs/**" - - "mkdocs.yml" - - "Makefile" - - ".github/workflows/docs.yml" + +permissions: + contents: read concurrency: group: documentation-${{ github.event.pull_request.number || github.ref }} @@ -23,17 +21,18 @@ jobs: documentation-build: name: Documentation build runs-on: ubuntu-latest + timeout-minutes: 10 steps: - name: Check out repository - uses: actions/checkout@v5 + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - name: Set up uv - uses: astral-sh/setup-uv@v8.3.2 + uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 - name: Build documentation run: make docs-build - name: Upload documentation if: github.event_name == 'push' - uses: actions/upload-pages-artifact@v4 + uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4 with: path: site @@ -42,6 +41,7 @@ jobs: if: github.event_name == 'push' needs: documentation-build runs-on: ubuntu-latest + timeout-minutes: 10 permissions: pages: write id-token: write @@ -52,4 +52,4 @@ jobs: steps: - name: Deploy documentation to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4 diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index 145dc15..024bb1c 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -10,17 +10,25 @@ on: - ".github/workflows/frontend.yml" pull_request: +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: frontend-changes: name: Kotlin frontend change detection runs-on: ubuntu-latest + timeout-minutes: 5 outputs: relevant: ${{ steps.detect.outputs.relevant }} steps: - name: Check out repository history if: github.event_name == 'pull_request' - uses: actions/checkout@v5 + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 with: fetch-depth: 0 - name: Detect relevant changes @@ -35,17 +43,56 @@ jobs: echo "relevant=false" >> "$GITHUB_OUTPUT" fi + kotlin-format: + name: Kotlin formatting + needs: frontend-changes + if: needs.frontend-changes.outputs.relevant == 'true' + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Check out repository + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + - name: Set up Java and Gradle cache + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + java-version: "24" + distribution: temurin + cache: gradle + - name: Check Kotlin formatting + run: make check-kotlin-format + + kotlin-static-analysis: + name: Kotlin static analysis + needs: frontend-changes + if: needs.frontend-changes.outputs.relevant == 'true' + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - name: Check out repository + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + - name: Set up Java and Gradle cache + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + with: + java-version: "24" + distribution: temurin + cache: gradle + - name: Run Detekt static analysis + run: make lint-kotlin-detekt + kotlin-jvm-tests: name: Kotlin/JVM tests needs: frontend-changes if: needs.frontend-changes.outputs.relevant == 'true' runs-on: ubuntu-latest + timeout-minutes: 20 steps: - name: Check out repository - uses: actions/checkout@v5 + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - name: Set up Java and Gradle cache - uses: actions/setup-java@v5 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: java-version: "24" distribution: temurin @@ -58,12 +105,13 @@ jobs: needs: frontend-changes if: needs.frontend-changes.outputs.relevant == 'true' runs-on: ubuntu-latest + timeout-minutes: 20 steps: - name: Check out repository - uses: actions/checkout@v5 + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - name: Set up Java and Gradle cache - uses: actions/setup-java@v5 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: java-version: "24" distribution: temurin @@ -76,12 +124,13 @@ jobs: needs: frontend-changes if: needs.frontend-changes.outputs.relevant == 'true' runs-on: ubuntu-latest + timeout-minutes: 20 steps: - name: Check out repository - uses: actions/checkout@v5 + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - name: Set up Java and Gradle cache - uses: actions/setup-java@v5 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: java-version: "24" distribution: temurin @@ -94,12 +143,13 @@ jobs: needs: frontend-changes if: needs.frontend-changes.outputs.relevant == 'true' runs-on: ubuntu-latest + timeout-minutes: 20 steps: - name: Check out repository - uses: actions/checkout@v5 + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - name: Set up Java and Gradle cache - uses: actions/setup-java@v5 + uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: java-version: "24" distribution: temurin @@ -109,15 +159,18 @@ jobs: kotlin-frontend-gate: name: Kotlin frontend all clear - needs: [frontend-changes, kotlin-jvm-tests, android-unit-tests, javascript-tests, webassembly-tests] + needs: [frontend-changes, kotlin-format, kotlin-static-analysis, kotlin-jvm-tests, android-unit-tests, javascript-tests, webassembly-tests] if: always() runs-on: ubuntu-latest + timeout-minutes: 5 steps: - name: Verify Kotlin frontend jobs env: RELEVANT: ${{ needs.frontend-changes.outputs.relevant }} CHANGE_DETECTION: ${{ needs.frontend-changes.result }} + KOTLIN_FORMAT: ${{ needs.kotlin-format.result }} + KOTLIN_STATIC_ANALYSIS: ${{ needs.kotlin-static-analysis.result }} JVM_TESTS: ${{ needs.kotlin-jvm-tests.result }} ANDROID_TESTS: ${{ needs.android-unit-tests.result }} JAVASCRIPT_TESTS: ${{ needs.javascript-tests.result }} @@ -135,7 +188,7 @@ jobs: echo "::error::Kotlin change detection produced no decision" exit 1 fi - for result in "$JVM_TESTS" "$ANDROID_TESTS" "$JAVASCRIPT_TESTS" "$WEBASSEMBLY_TESTS"; do + for result in "$KOTLIN_FORMAT" "$KOTLIN_STATIC_ANALYSIS" "$JVM_TESTS" "$ANDROID_TESTS" "$JAVASCRIPT_TESTS" "$WEBASSEMBLY_TESTS"; do if [[ "$result" != "$expected" ]]; then echo "::error::Expected Kotlin job status $expected, received $result" exit 1 diff --git a/.github/workflows/markdown.yml b/.github/workflows/markdown.yml index d4293c8..7f4d340 100644 --- a/.github/workflows/markdown.yml +++ b/.github/workflows/markdown.yml @@ -5,18 +5,26 @@ on: branches: [main] pull_request: +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: markdown-lint: name: Markdown style runs-on: ubuntu-latest + timeout-minutes: 10 steps: - name: Check out repository - uses: actions/checkout@v5 + uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 - name: Set up Node.js and npm cache - uses: actions/setup-node@v5 + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 with: - node-version: lts/* + node-version: 24 cache: npm - name: Install Markdown tooling run: npm ci diff --git a/Makefile b/Makefile index 567cd6d..d7f341d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help lint lint-kotlin lint-rust lint-markdown \ +.PHONY: help lint lint-kotlin lint-kotlin-detekt lint-rust lint-api lint-markdown \ format format-kotlin format-rust check-format check-kotlin-format check-rust-format \ test test-frontend test-frontend-jvm test-frontend-android test-frontend-js test-frontend-wasm test-backend \ build build-desktop build-android build-ios clean \ @@ -9,8 +9,9 @@ GRADLE := ./frontend/gradlew -p frontend MARKDOWNLINT := npx markdownlint-cli2 +REDOCLY := npx redocly GRADLE_FLAGS := --no-daemon -MKDOCS := uv run --with "mkdocs-material==9.7.7" mkdocs +MKDOCS := uvx --with "mkdocs-material==9.7.7" mkdocs export NO_MKDOCS_2_WARNING := true # Help @@ -19,9 +20,11 @@ help: ## Show this help @echo "Chores App — Makefile targets" @echo "" @echo " CODE QUALITY" - @echo " lint Run all linters (Kotlin + Rust + Markdown)" + @echo " lint Run all linters" @echo " lint-kotlin Run ktlint + detekt" + @echo " lint-kotlin-detekt Run Detekt static analysis" @echo " lint-rust Run Clippy with warnings denied" + @echo " lint-api Validate the OpenAPI contract" @echo " lint-markdown Run markdownlint-cli2" @echo " format Auto-format Kotlin and Rust" @echo " check-format Check Kotlin and Rust formatting" @@ -55,18 +58,18 @@ help: ## Show this help # Setup setup: ## Install all tooling dependencies (npm + python) - @echo "→ Installing markdownlint-cli2..." - npm install --save-dev markdownlint-cli2 + @echo "→ Installing Node.js tooling..." + npm install @echo "→ Preparing documentation tools..." $(MKDOCS) --version # Lint -lint: lint-kotlin lint-rust lint-markdown ## Run all linters +lint: lint-kotlin lint-rust lint-api lint-markdown ## Run all linters -lint-kotlin: ## Run ktlint + detekt - @echo "→ Running ktlint..." - $(GRADLE) ktlintCheck $(GRADLE_FLAGS) +lint-kotlin: check-kotlin-format lint-kotlin-detekt ## Run ktlint + detekt + +lint-kotlin-detekt: ## Run Detekt static analysis @echo "→ Running detekt..." $(GRADLE) detekt $(GRADLE_FLAGS) @@ -74,6 +77,10 @@ lint-rust: ## Run Clippy with warnings denied @echo "→ Running Clippy with warnings denied..." cargo clippy --manifest-path backend/Cargo.toml --all-targets --all-features --locked -- -D warnings +lint-api: ## Validate the OpenAPI contract + @echo "→ Validating OpenAPI contract..." + $(REDOCLY) lint api/openapi.yaml + lint-markdown: ## Run markdownlint-cli2 @echo "→ Running markdownlint-cli2..." $(MARKDOWNLINT) @@ -168,6 +175,6 @@ docs-build: ## Build static documentation site # CI -ci: lint-markdown check-rust-format lint-rust test ## Run the same checks as CI +ci: lint-markdown lint-api check-kotlin-format lint-kotlin-detekt check-rust-format lint-rust test ## Run the same checks as CI @echo "" @echo "✔ CI checks passed" diff --git a/api/openapi.yaml b/api/openapi.yaml index 8a035b2..8e2251b 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -2,4 +2,9 @@ openapi: 3.1.0 info: title: Chores API version: 0.1.0 + license: + name: GNU Affero General Public License v3.0 + identifier: AGPL-3.0-only +servers: + - url: / paths: {} diff --git a/frontend/build.gradle.kts b/frontend/build.gradle.kts index 14c7437..8627ab7 100644 --- a/frontend/build.gradle.kts +++ b/frontend/build.gradle.kts @@ -7,6 +7,27 @@ plugins { alias(libs.plugins.composeMultiplatform) apply false alias(libs.plugins.composeCompiler) apply false alias(libs.plugins.kotlinMultiplatform) apply false - alias(libs.plugins.ktlint) - alias(libs.plugins.detekt) + alias(libs.plugins.ktlint) apply false + alias(libs.plugins.detekt) apply false +} + +subprojects { + apply(plugin = "org.jlleitschuh.gradle.ktlint") + apply(plugin = "io.gitlab.arturbosch.detekt") + + extensions.configure { + config.setFrom(rootProject.files("detekt-config.yml")) + source.setFrom(fileTree("src") { include("**/*.kt") }) + } + tasks.withType().configureEach { + jvmTarget = "11" + } + extensions.configure { + additionalEditorconfig.set( + mapOf("ktlint_function_naming_ignore_when_annotated_with" to "Composable"), + ) + } + tasks.withType().configureEach { + exclude { it.file.path.contains("/generated/") } + } } diff --git a/frontend/composeApp/build.gradle.kts b/frontend/composeApp/build.gradle.kts index bd531d0..63de48b 100644 --- a/frontend/composeApp/build.gradle.kts +++ b/frontend/composeApp/build.gradle.kts @@ -16,30 +16,30 @@ kotlin { jvmTarget.set(JvmTarget.JVM_11) } } - + listOf( iosArm64(), - iosSimulatorArm64() + iosSimulatorArm64(), ).forEach { iosTarget -> iosTarget.binaries.framework { baseName = "ComposeApp" isStatic = true } } - + jvm() - + js { browser() binaries.executable() } - + @OptIn(ExperimentalWasmDsl::class) wasmJs { browser() binaries.executable() } - + sourceSets { androidMain.dependencies { implementation(libs.compose.uiToolingPreview) diff --git a/frontend/composeApp/src/androidMain/kotlin/org/nznyx/choresapp/MainActivity.kt b/frontend/composeApp/src/androidMain/kotlin/org/nznyx/choresapp/MainActivity.kt index c6bff5c..bff4c26 100644 --- a/frontend/composeApp/src/androidMain/kotlin/org/nznyx/choresapp/MainActivity.kt +++ b/frontend/composeApp/src/androidMain/kotlin/org/nznyx/choresapp/MainActivity.kt @@ -22,4 +22,4 @@ class MainActivity : ComponentActivity() { @Composable fun AppAndroidPreview() { App() -} \ No newline at end of file +} diff --git a/frontend/composeApp/src/commonMain/kotlin/org/nznyx/choresapp/App.kt b/frontend/composeApp/src/commonMain/kotlin/org/nznyx/choresapp/App.kt index dce4c22..389135c 100644 --- a/frontend/composeApp/src/commonMain/kotlin/org/nznyx/choresapp/App.kt +++ b/frontend/composeApp/src/commonMain/kotlin/org/nznyx/choresapp/App.kt @@ -10,14 +10,17 @@ import androidx.compose.foundation.layout.safeContentPadding import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview -import org.jetbrains.compose.resources.painterResource - import choresapp.composeapp.generated.resources.Res import choresapp.composeapp.generated.resources.compose_multiplatform +import org.jetbrains.compose.resources.painterResource @Composable @Preview @@ -25,10 +28,11 @@ fun App() { MaterialTheme { var showContent by remember { mutableStateOf(false) } Column( - modifier = Modifier - .background(MaterialTheme.colorScheme.primaryContainer) - .safeContentPadding() - .fillMaxSize(), + modifier = + Modifier + .background(MaterialTheme.colorScheme.primaryContainer) + .safeContentPadding() + .fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, ) { Button(onClick = { showContent = !showContent }) { @@ -46,4 +50,4 @@ fun App() { } } } -} \ No newline at end of file +} diff --git a/frontend/composeApp/src/commonTest/kotlin/org/nznyx/choresapp/ComposeAppCommonTest.kt b/frontend/composeApp/src/commonTest/kotlin/org/nznyx/choresapp/ComposeAppCommonTest.kt index d011b68..f2ce5be 100644 --- a/frontend/composeApp/src/commonTest/kotlin/org/nznyx/choresapp/ComposeAppCommonTest.kt +++ b/frontend/composeApp/src/commonTest/kotlin/org/nznyx/choresapp/ComposeAppCommonTest.kt @@ -4,9 +4,8 @@ import kotlin.test.Test import kotlin.test.assertEquals class ComposeAppCommonTest { - @Test fun example() { assertEquals(3, 1 + 2) } -} \ No newline at end of file +} diff --git a/frontend/composeApp/src/iosMain/kotlin/org/nznyx/choresapp/MainViewController.kt b/frontend/composeApp/src/iosMain/kotlin/org/nznyx/choresapp/MainViewController.kt index 5830ac1..292d58c 100644 --- a/frontend/composeApp/src/iosMain/kotlin/org/nznyx/choresapp/MainViewController.kt +++ b/frontend/composeApp/src/iosMain/kotlin/org/nznyx/choresapp/MainViewController.kt @@ -2,4 +2,5 @@ package org.nznyx.choresapp import androidx.compose.ui.window.ComposeUIViewController -fun MainViewController() = ComposeUIViewController { App() } \ No newline at end of file +@Suppress("ktlint:standard:function-naming") +fun MainViewController() = ComposeUIViewController { App() } diff --git a/frontend/composeApp/src/jvmMain/kotlin/org/nznyx/choresapp/Main.kt b/frontend/composeApp/src/jvmMain/kotlin/org/nznyx/choresapp/Main.kt new file mode 100644 index 0000000..6cbedb0 --- /dev/null +++ b/frontend/composeApp/src/jvmMain/kotlin/org/nznyx/choresapp/Main.kt @@ -0,0 +1,14 @@ +package org.nznyx.choresapp + +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application + +fun main() = + application { + Window( + onCloseRequest = ::exitApplication, + title = "ChoresApp", + ) { + App() + } + } diff --git a/frontend/composeApp/src/jvmMain/kotlin/org/nznyx/choresapp/main.kt b/frontend/composeApp/src/jvmMain/kotlin/org/nznyx/choresapp/main.kt deleted file mode 100644 index a0a29fe..0000000 --- a/frontend/composeApp/src/jvmMain/kotlin/org/nznyx/choresapp/main.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.nznyx.choresapp - -import androidx.compose.ui.window.Window -import androidx.compose.ui.window.application - -fun main() = application { - Window( - onCloseRequest = ::exitApplication, - title = "ChoresApp", - ) { - App() - } -} \ No newline at end of file diff --git a/frontend/composeApp/src/webMain/kotlin/org/nznyx/choresapp/main.kt b/frontend/composeApp/src/webMain/kotlin/org/nznyx/choresapp/Main.kt similarity index 99% rename from frontend/composeApp/src/webMain/kotlin/org/nznyx/choresapp/main.kt rename to frontend/composeApp/src/webMain/kotlin/org/nznyx/choresapp/Main.kt index dcb1074..329fdfc 100644 --- a/frontend/composeApp/src/webMain/kotlin/org/nznyx/choresapp/main.kt +++ b/frontend/composeApp/src/webMain/kotlin/org/nznyx/choresapp/Main.kt @@ -8,4 +8,4 @@ fun main() { ComposeViewport { App() } -} \ No newline at end of file +} diff --git a/frontend/detekt-config.yml b/frontend/detekt-config.yml index 1024491..27f46cc 100644 --- a/frontend/detekt-config.yml +++ b/frontend/detekt-config.yml @@ -31,7 +31,7 @@ complexity: threshold: 4 ComplexInterface: active: false - ComplexMethod: + CyclomaticComplexMethod: active: true threshold: 15 LargeClass: @@ -42,15 +42,14 @@ complexity: threshold: 60 LongParameterList: active: true - threshold: 6 - TooManyFunctions: - active: true - threshold: 11 + functionThreshold: 6 + constructorThreshold: 6 style: active: true MagicNumber: active: true + excludes: ["**/*Test.kt"] MaxLineLength: active: true maxLineLength: 120 diff --git a/frontend/shared/build.gradle.kts b/frontend/shared/build.gradle.kts index 8fc4bfb..31ffa7c 100644 --- a/frontend/shared/build.gradle.kts +++ b/frontend/shared/build.gradle.kts @@ -12,21 +12,21 @@ kotlin { jvmTarget.set(JvmTarget.JVM_11) } } - + iosArm64() iosSimulatorArm64() - + jvm() - + js { browser() } - + @OptIn(ExperimentalWasmDsl::class) wasmJs { browser() } - + sourceSets { commonMain.dependencies { // put your Multiplatform dependencies here diff --git a/frontend/shared/src/androidMain/kotlin/org/nznyx/choresapp/Platform.android.kt b/frontend/shared/src/androidMain/kotlin/org/nznyx/choresapp/Platform.android.kt index 5982771..66875d6 100644 --- a/frontend/shared/src/androidMain/kotlin/org/nznyx/choresapp/Platform.android.kt +++ b/frontend/shared/src/androidMain/kotlin/org/nznyx/choresapp/Platform.android.kt @@ -6,4 +6,4 @@ class AndroidPlatform : Platform { override val name: String = "Android ${Build.VERSION.SDK_INT}" } -actual fun getPlatform(): Platform = AndroidPlatform() \ No newline at end of file +actual fun getPlatform(): Platform = AndroidPlatform() diff --git a/frontend/shared/src/commonMain/kotlin/org/nznyx/choresapp/Constants.kt b/frontend/shared/src/commonMain/kotlin/org/nznyx/choresapp/Constants.kt index 882f3aa..db1ec65 100644 --- a/frontend/shared/src/commonMain/kotlin/org/nznyx/choresapp/Constants.kt +++ b/frontend/shared/src/commonMain/kotlin/org/nznyx/choresapp/Constants.kt @@ -1,3 +1,3 @@ package org.nznyx.choresapp -const val SERVER_PORT = 8080 \ No newline at end of file +const val SERVER_PORT = 8080 diff --git a/frontend/shared/src/commonMain/kotlin/org/nznyx/choresapp/Greeting.kt b/frontend/shared/src/commonMain/kotlin/org/nznyx/choresapp/Greeting.kt index 3312cf3..cffffcb 100644 --- a/frontend/shared/src/commonMain/kotlin/org/nznyx/choresapp/Greeting.kt +++ b/frontend/shared/src/commonMain/kotlin/org/nznyx/choresapp/Greeting.kt @@ -6,4 +6,4 @@ class Greeting { fun greet(): String { return "Hello, ${platform.name}!" } -} \ No newline at end of file +} diff --git a/frontend/shared/src/commonMain/kotlin/org/nznyx/choresapp/Platform.kt b/frontend/shared/src/commonMain/kotlin/org/nznyx/choresapp/Platform.kt index 7ae4614..9bbdb9e 100644 --- a/frontend/shared/src/commonMain/kotlin/org/nznyx/choresapp/Platform.kt +++ b/frontend/shared/src/commonMain/kotlin/org/nznyx/choresapp/Platform.kt @@ -4,4 +4,4 @@ interface Platform { val name: String } -expect fun getPlatform(): Platform \ No newline at end of file +expect fun getPlatform(): Platform diff --git a/frontend/shared/src/commonTest/kotlin/org/nznyx/choresapp/SharedCommonTest.kt b/frontend/shared/src/commonTest/kotlin/org/nznyx/choresapp/SharedCommonTest.kt index 7d38dab..204ec57 100644 --- a/frontend/shared/src/commonTest/kotlin/org/nznyx/choresapp/SharedCommonTest.kt +++ b/frontend/shared/src/commonTest/kotlin/org/nznyx/choresapp/SharedCommonTest.kt @@ -4,9 +4,8 @@ import kotlin.test.Test import kotlin.test.assertEquals class SharedCommonTest { - @Test fun example() { assertEquals(3, 1 + 2) } -} \ No newline at end of file +} diff --git a/frontend/shared/src/iosMain/kotlin/org/nznyx/choresapp/Platform.ios.kt b/frontend/shared/src/iosMain/kotlin/org/nznyx/choresapp/Platform.ios.kt index 6dbc04c..1bce6ae 100644 --- a/frontend/shared/src/iosMain/kotlin/org/nznyx/choresapp/Platform.ios.kt +++ b/frontend/shared/src/iosMain/kotlin/org/nznyx/choresapp/Platform.ios.kt @@ -2,8 +2,8 @@ package org.nznyx.choresapp import platform.UIKit.UIDevice -class IOSPlatform: Platform { +class IOSPlatform : Platform { override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion } -actual fun getPlatform(): Platform = IOSPlatform() \ No newline at end of file +actual fun getPlatform(): Platform = IOSPlatform() diff --git a/frontend/shared/src/jsMain/kotlin/org/nznyx/choresapp/Platform.js.kt b/frontend/shared/src/jsMain/kotlin/org/nznyx/choresapp/Platform.js.kt index 0aca794..b8666a3 100644 --- a/frontend/shared/src/jsMain/kotlin/org/nznyx/choresapp/Platform.js.kt +++ b/frontend/shared/src/jsMain/kotlin/org/nznyx/choresapp/Platform.js.kt @@ -1,7 +1,7 @@ package org.nznyx.choresapp -class JsPlatform: Platform { +class JsPlatform : Platform { override val name: String = "Web with Kotlin/JS" } -actual fun getPlatform(): Platform = JsPlatform() \ No newline at end of file +actual fun getPlatform(): Platform = JsPlatform() diff --git a/frontend/shared/src/jvmMain/kotlin/org/nznyx/choresapp/Platform.jvm.kt b/frontend/shared/src/jvmMain/kotlin/org/nznyx/choresapp/Platform.jvm.kt index 982b82e..30445c0 100644 --- a/frontend/shared/src/jvmMain/kotlin/org/nznyx/choresapp/Platform.jvm.kt +++ b/frontend/shared/src/jvmMain/kotlin/org/nznyx/choresapp/Platform.jvm.kt @@ -1,7 +1,7 @@ package org.nznyx.choresapp -class JVMPlatform: Platform { +class JVMPlatform : Platform { override val name: String = "Java ${System.getProperty("java.version")}" } -actual fun getPlatform(): Platform = JVMPlatform() \ No newline at end of file +actual fun getPlatform(): Platform = JVMPlatform() diff --git a/frontend/shared/src/wasmJsMain/kotlin/org/nznyx/choresapp/Platform.wasmJs.kt b/frontend/shared/src/wasmJsMain/kotlin/org/nznyx/choresapp/Platform.wasmJs.kt index 9ef6c7f..c572d62 100644 --- a/frontend/shared/src/wasmJsMain/kotlin/org/nznyx/choresapp/Platform.wasmJs.kt +++ b/frontend/shared/src/wasmJsMain/kotlin/org/nznyx/choresapp/Platform.wasmJs.kt @@ -1,7 +1,7 @@ package org.nznyx.choresapp -class WasmPlatform: Platform { +class WasmPlatform : Platform { override val name: String = "Web with Kotlin/Wasm" } -actual fun getPlatform(): Platform = WasmPlatform() \ No newline at end of file +actual fun getPlatform(): Platform = WasmPlatform() diff --git a/package-lock.json b/package-lock.json index ddca61b..1d87d1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,8 @@ "": { "name": "chores-app", "devDependencies": { - "markdownlint-cli2": "^0.23.0" + "@redocly/cli": "2.46.1", + "markdownlint-cli2": "0.23.2" } }, "node_modules/@nodelib/fs.scandir": { @@ -47,6 +48,21 @@ "node": ">= 8" } }, + "node_modules/@redocly/cli": { + "version": "2.46.1", + "resolved": "https://registry.npmjs.org/@redocly/cli/-/cli-2.46.1.tgz", + "integrity": "sha512-FSUSq2FU8VN7DmTobTmq7zb3zPnDkRKSMd3n38D62JFJUUZKnLMQtn7BUhsD2dCFH2WgF05I3fbN7QHDQyCBeA==", + "dev": true, + "license": "MIT", + "bin": { + "openapi": "bin/cli.js", + "redocly": "bin/cli.js" + }, + "engines": { + "node": ">=22.12.0 || >=20.19.0 <21.0.0", + "npm": ">=10" + } + }, "node_modules/@sindresorhus/merge-streams": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", @@ -303,9 +319,9 @@ } }, "node_modules/globby": { - "version": "16.2.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-16.2.1.tgz", - "integrity": "sha512-JmsqJalahxxgW8V2ecSQ2G7UjPlI9cpKdrkG9KoNiXhd/YslXOTEB0cViENWUznuovIuNT+FkMbraDGjr4FCUg==", + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-16.2.2.tgz", + "integrity": "sha512-NLvV9ubZ6NDsJaOpKPy3cQeJpKi9DcWiyCiFUpJPA0YihRqiE6RWaLUmgNNPr8MgPpLZjnBjSmou7uZBRJv9wA==", "dev": true, "license": "MIT", "dependencies": { @@ -428,9 +444,9 @@ } }, "node_modules/js-yaml": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.2.1.tgz", - "integrity": "sha512-zfLtNfQqxVqq3uaTqSkh4x4hZw3KHobGUA0fJUj4wawW8bsQLTVqpHdXSIzidh7o+4lEW36tANuAGdaFx6Zgnw==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.2.2.tgz", + "integrity": "sha512-dayzUzKkJ1MkuUtZglSebU43utNXH0OWQByK9rKOOuYIO8M5TV1y+n8ALMdG0rdzBnfNkOmZEqrURepb0ejqBw==", "dev": true, "funding": [ { @@ -557,14 +573,14 @@ } }, "node_modules/markdownlint-cli2": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.23.1.tgz", - "integrity": "sha512-20JPI5W+HpV1OA+pUM712wgvL4GzYNUvbmhLU8KlEYJ1kCDx4soZ4/Xqd+WkLrPTOKMAn8SfO3zYFrK8GLlwQg==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/markdownlint-cli2/-/markdownlint-cli2-0.23.2.tgz", + "integrity": "sha512-eUhcnkSpzURo/o4htSqc7LPDszgOOTknhU4eY/sPHvMCLxnTCYscv1gw1/js/idmaZPisv9ECVEIORcllqjTUw==", "dev": true, "license": "MIT", "dependencies": { - "globby": "16.2.1", - "js-yaml": "5.2.1", + "globby": "16.2.2", + "js-yaml": "5.2.2", "jsonc-parser": "3.3.1", "jsonpointer": "5.0.1", "markdown-it": "14.3.0", diff --git a/package.json b/package.json index 03d7a4b..de5fd44 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { "name": "chores-app", "private": true, - "description": "Markdown tooling for the Chores App monorepo.", + "description": "Repository tooling for the Chores App monorepo.", "devDependencies": { - "markdownlint-cli2": "^0.23.0" + "@redocly/cli": "2.46.1", + "markdownlint-cli2": "0.23.2" } } diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..9946197 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "1.97.1" +components = ["clippy", "rustfmt"] +profile = "minimal" From 4cdcf4442b88782986e99387dc30b5bcc7af8c64 Mon Sep 17 00:00:00 2001 From: Aleksei Tokarev Date: Tue, 18 Aug 2026 15:55:46 +0300 Subject: [PATCH 2/3] ci: run Dependabot monthly --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6c5049e..8f2ceac 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -3,7 +3,7 @@ updates: - package-ecosystem: github-actions directory: / schedule: - interval: weekly + interval: monthly groups: github-actions: patterns: From 6a206dc2b1d3e8d517cce070cca55e746e41416a Mon Sep 17 00:00:00 2001 From: Aleksei Tokarev Date: Tue, 18 Aug 2026 15:56:45 +0300 Subject: [PATCH 3/3] ci: use checkout v5 tag --- .github/workflows/api.yml | 2 +- .github/workflows/backend.yml | 8 ++++---- .github/workflows/docs.yml | 2 +- .github/workflows/frontend.yml | 14 +++++++------- .github/workflows/markdown.yml | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/api.yml b/.github/workflows/api.yml index 173e071..53677cd 100644 --- a/.github/workflows/api.yml +++ b/.github/workflows/api.yml @@ -32,7 +32,7 @@ jobs: steps: - name: Check out repository - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + uses: actions/checkout@v5 - name: Set up Node.js and npm cache uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 with: diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml index a5b3708..c48fde6 100644 --- a/.github/workflows/backend.yml +++ b/.github/workflows/backend.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Check out repository history if: github.event_name == 'pull_request' - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Detect relevant changes @@ -53,7 +53,7 @@ jobs: steps: - name: Check out repository - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + uses: actions/checkout@v5 - name: Check Rust formatting run: make check-rust-format @@ -66,7 +66,7 @@ jobs: steps: - name: Check out repository - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + uses: actions/checkout@v5 - name: Run Clippy with warnings denied run: make lint-rust @@ -79,7 +79,7 @@ jobs: steps: - name: Check out repository - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + uses: actions/checkout@v5 - name: Run Rust tests run: make test-backend diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index b960c53..42bac6e 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Check out repository - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + uses: actions/checkout@v5 - name: Set up uv uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2 - name: Build documentation diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index 024bb1c..8aa1540 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -28,7 +28,7 @@ jobs: steps: - name: Check out repository history if: github.event_name == 'pull_request' - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Detect relevant changes @@ -52,7 +52,7 @@ jobs: steps: - name: Check out repository - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + uses: actions/checkout@v5 - name: Set up Java and Gradle cache uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: @@ -71,7 +71,7 @@ jobs: steps: - name: Check out repository - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + uses: actions/checkout@v5 - name: Set up Java and Gradle cache uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: @@ -90,7 +90,7 @@ jobs: steps: - name: Check out repository - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + uses: actions/checkout@v5 - name: Set up Java and Gradle cache uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: @@ -109,7 +109,7 @@ jobs: steps: - name: Check out repository - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + uses: actions/checkout@v5 - name: Set up Java and Gradle cache uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: @@ -128,7 +128,7 @@ jobs: steps: - name: Check out repository - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + uses: actions/checkout@v5 - name: Set up Java and Gradle cache uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: @@ -147,7 +147,7 @@ jobs: steps: - name: Check out repository - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + uses: actions/checkout@v5 - name: Set up Java and Gradle cache uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: diff --git a/.github/workflows/markdown.yml b/.github/workflows/markdown.yml index 7f4d340..98058ea 100644 --- a/.github/workflows/markdown.yml +++ b/.github/workflows/markdown.yml @@ -20,7 +20,7 @@ jobs: steps: - name: Check out repository - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 + uses: actions/checkout@v5 - name: Set up Node.js and npm cache uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5 with: