ci: add CI/CD workflows, dependabot, and release signing#2
Conversation
- CI workflow: build debug APK + lint on push/PR to main - Release workflow: signed AAB+APK on version tags (v*) - Dependabot: weekly Gradle, monthly GitHub Actions, grouped updates - GitGuardian: suppress false positives on build.gradle.kts - build.gradle.kts: dynamic VERSION_NAME/VERSION_CODE from Gradle properties, CASHPILOT_* signing config, buildConfig enabled, modern kotlin compilerOptions DSL
📝 WalkthroughWalkthroughAdds CI and Release GitHub Actions workflows, Dependabot and GitGuardian configs; updates Gradle settings and app build script for property-based versioning, conditional signing, Kotlin DSL JVM target, BuildConfig generation, and adds an Android lint baseline and a minor map-access tweak in notification listener. Changes
Sequence Diagram(s)sequenceDiagram
participant GH as "GitHub (push/tag)"
participant Runner as "Actions Runner"
participant Gradle as "Gradle"
participant Secrets as "Secrets (KEYSTORE_BASE64)"
participant Storage as "Artifacts / Releases"
GH->>Runner: push / pull_request (main) -> trigger CI
Runner->>Gradle: checkout, setup JDK/Gradle
Runner->>Gradle: ./gradlew assembleDebug, lintDebug
Gradle-->>Runner: debug APK + lint results
Runner->>Storage: upload artifact (app-debug)
Note over GH,Runner: Release flow (on tag v*)
GH->>Runner: push tag vX.Y.Z -> trigger Release
Runner->>Runner: extract VERSION_NAME / VERSION_CODE
Runner->>Secrets: request KEYSTORE_BASE64
Secrets-->>Runner: keystore blob
Runner->>Gradle: ./gradlew bundleRelease assembleRelease (with signing props)
Gradle-->>Runner: AAB / APK
Runner->>Storage: create GitHub Release + upload AAB/APK
Runner->>Runner: delete decoded keystore (always)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
.github/workflows/release.yml (2)
63-65: Publish the ProGuard mapping file with the release artifacts.
releaseminification is enabled inapp/build.gradle.kts, so omittingapp/build/outputs/mapping/release/mapping.txtmakes post-release stack traces much harder to deobfuscate.💡 Proposed fix
files: | app/build/outputs/bundle/release/app-release.aab app/build/outputs/apk/release/app-release.apk + app/build/outputs/mapping/release/mapping.txt🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/release.yml around lines 63 - 65, Update the artifact list in the release workflow so the ProGuard/R8 mapping file produced by the release build is published alongside the APK/AAB; specifically, in the files: block that currently lists app-release.aab and app-release.apk, add the release mapping artifact (the mapping.txt generated under the build outputs mapping/release directory) so deobfuscation info is available for post-release stacktrace analysis.
38-39: Fail fast when the signing secrets are empty.Decoding an empty
KEYSTORE_BASE64still leaves a keystore file on disk, so the job fails later during signing with a much less actionable error. Add a quick preflight check for the four required secrets before decoding/building.💡 Proposed fix
+ - name: Validate release secrets + env: + KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }} + KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} + KEY_ALIAS: ${{ secrets.KEY_ALIAS }} + KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} + run: | + for name in KEYSTORE_BASE64 KEYSTORE_PASSWORD KEY_ALIAS KEY_PASSWORD; do + if [ -z "${!name}" ]; then + echo "Missing required secret: $name" >&2 + exit 1 + fi + done + - name: Decode keystore run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > app/release.keystore🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/release.yml around lines 38 - 39, Add a fail-fast preflight step before the "Decode keystore" step that verifies the required signing secrets (at minimum KEYSTORE_BASE64, KEYSTORE_PASSWORD, KEY_ALIAS, KEY_PASSWORD) are non-empty and exits the job with a clear message if any are missing; locate the step named "Decode keystore" and insert a preceding step (e.g., "Validate signing secrets") that checks each secret and fails early so the subsequent decode/build/sign steps do not produce confusing errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.gitguardian.yml:
- Around line 1-2: The .gitguardian.yml currently uses the paths-ignore entry to
skip the entire Gradle script (the "paths-ignore" key with the
"app/build.gradle.kts" pattern); remove that whole-file exclusion and instead
add a narrow allowlist/ignore rule that targets only the known false-positive
token pattern introduced for release signing (e.g., the exact string or a
specific regex for the signing input) so the rest of app/build.gradle.kts
remains scanned; update the "paths-ignore" block to no longer contain the full
filename and replace it with a rule that matches only the false-positive
pattern.
In @.github/workflows/ci.yml:
- Around line 9-12: Add explicit least-privileged GITHUB_TOKEN permissions for
the CI job by declaring permissions: contents: read for the build job; update
the jobs -> build block (the job named "build" that contains runs-on:
ubuntu-latest and steps) to include a permissions entry with contents: read so
the workflow does not inherit broader repo defaults.
In @.github/workflows/release.yml:
- Around line 17-27: The tag parsing step (variables TAG, VERSION, MAJOR, MINOR,
PATCH and computed CODE) accepts many malformed tags and can produce collisions;
change the script to first validate TAG/VERSION against a strict semver patch
regex (e.g. ^v[0-9]+\.[0-9]+\.[0-9]+$) and exit non‑zero with a clear error if
it doesn't match, then safely split VERSION into MAJOR, MINOR, PATCH and compute
CODE (or use larger multipliers) only for validated input so invalid tags like
v1.2, vfoo, or v1.2.3-rc1 are rejected before computing CODE.
In `@app/build.gradle.kts`:
- Around line 63-66: The kotlin { compilerOptions { jvmTarget.set(...) } } block
is currently nested inside the android { } block; move this top-level Kotlin
Gradle plugin configuration out of android by placing the kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) } } block at the
project/module root (same level as android { } and plugins), removing the nested
version so the Kotlin compilerOptions are defined as a top-level extension
rather than inside the android block.
---
Nitpick comments:
In @.github/workflows/release.yml:
- Around line 63-65: Update the artifact list in the release workflow so the
ProGuard/R8 mapping file produced by the release build is published alongside
the APK/AAB; specifically, in the files: block that currently lists
app-release.aab and app-release.apk, add the release mapping artifact (the
mapping.txt generated under the build outputs mapping/release directory) so
deobfuscation info is available for post-release stacktrace analysis.
- Around line 38-39: Add a fail-fast preflight step before the "Decode keystore"
step that verifies the required signing secrets (at minimum KEYSTORE_BASE64,
KEYSTORE_PASSWORD, KEY_ALIAS, KEY_PASSWORD) are non-empty and exits the job with
a clear message if any are missing; locate the step named "Decode keystore" and
insert a preceding step (e.g., "Validate signing secrets") that checks each
secret and fails early so the subsequent decode/build/sign steps do not produce
confusing errors.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: aca5bb78-8e39-48b6-9814-88f3f4cd3948
📒 Files selected for processing (5)
.gitguardian.yml.github/dependabot.yml.github/workflows/ci.yml.github/workflows/release.ymlapp/build.gradle.kts
| paths-ignore: | ||
| - "app/build.gradle.kts" |
There was a problem hiding this comment.
Don't exclude the whole Gradle script from secret scanning.
app/build.gradle.kts now wires release-signing inputs. Ignoring the entire file hides any real credential accidentally committed there later; narrow this to the specific false-positive pattern instead.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.gitguardian.yml around lines 1 - 2, The .gitguardian.yml currently uses the
paths-ignore entry to skip the entire Gradle script (the "paths-ignore" key with
the "app/build.gradle.kts" pattern); remove that whole-file exclusion and
instead add a narrow allowlist/ignore rule that targets only the known
false-positive token pattern introduced for release signing (e.g., the exact
string or a specific regex for the signing input) so the rest of
app/build.gradle.kts remains scanned; update the "paths-ignore" block to no
longer contain the full filename and replace it with a rule that matches only
the false-positive pattern.
| jobs: | ||
| build: | ||
| runs-on: ubuntu-latest | ||
| steps: |
There was a problem hiding this comment.
Set explicit read-only token permissions for CI.
This job runs PR code via ./gradlew, but it inherits whatever default GITHUB_TOKEN scope the repository is configured with. Declare contents: read here so the workflow stays least-privileged even if repo defaults change.
🔒 Proposed fix
jobs:
build:
runs-on: ubuntu-latest
+ permissions:
+ contents: read
steps:📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| steps: | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/ci.yml around lines 9 - 12, Add explicit least-privileged
GITHUB_TOKEN permissions for the CI job by declaring permissions: contents: read
for the build job; update the jobs -> build block (the job named "build" that
contains runs-on: ubuntu-latest and steps) to include a permissions entry with
contents: read so the workflow does not inherit broader repo defaults.
| - name: Compute version from tag | ||
| id: version | ||
| run: | | ||
| TAG="${GITHUB_REF#refs/tags/}" | ||
| VERSION="${TAG#v}" | ||
| # versionCode: major*10000 + minor*100 + patch (e.g. 1.2.3 -> 10203) | ||
| IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION" | ||
| CODE=$(( ${MAJOR:-0} * 10000 + ${MINOR:-0} * 100 + ${PATCH:-0} )) | ||
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | ||
| echo "name=$VERSION" >> "$GITHUB_OUTPUT" | ||
| echo "code=$CODE" >> "$GITHUB_OUTPUT" |
There was a problem hiding this comment.
Validate the tag before deriving VERSION_CODE.
v* accepts tags this parser cannot safely encode (v1.2, vfoo, v1.2.3-rc1), and major*10000 + minor*100 + patch collides once minor or patch reaches 100. Fail fast on a strict vMAJOR.MINOR.PATCH tag, or widen the radix.
💡 Proposed fix
- name: Compute version from tag
id: version
run: |
TAG="${GITHUB_REF#refs/tags/}"
- VERSION="${TAG#v}"
- # versionCode: major*10000 + minor*100 + patch (e.g. 1.2.3 -> 10203)
- IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION"
- CODE=$(( ${MAJOR:-0} * 10000 + ${MINOR:-0} * 100 + ${PATCH:-0} ))
+ if [[ ! "$TAG" =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then
+ echo "Expected tag format vMAJOR.MINOR.PATCH, got: $TAG" >&2
+ exit 1
+ fi
+ MAJOR="${BASH_REMATCH[1]}"
+ MINOR="${BASH_REMATCH[2]}"
+ PATCH="${BASH_REMATCH[3]}"
+ if (( MINOR > 99 || PATCH > 99 )); then
+ echo "MINOR and PATCH must be <= 99 for the current versionCode scheme" >&2
+ exit 1
+ fi
+ VERSION="${MAJOR}.${MINOR}.${PATCH}"
+ CODE=$(( MAJOR * 10000 + MINOR * 100 + PATCH ))
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "name=$VERSION" >> "$GITHUB_OUTPUT"
echo "code=$CODE" >> "$GITHUB_OUTPUT"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/release.yml around lines 17 - 27, The tag parsing step
(variables TAG, VERSION, MAJOR, MINOR, PATCH and computed CODE) accepts many
malformed tags and can produce collisions; change the script to first validate
TAG/VERSION against a strict semver patch regex (e.g. ^v[0-9]+\.[0-9]+\.[0-9]+$)
and exit non‑zero with a clear error if it doesn't match, then safely split
VERSION into MAJOR, MINOR, PATCH and compute CODE (or use larger multipliers)
only for validated input so invalid tags like v1.2, vfoo, or v1.2.3-rc1 are
rejected before computing CODE.
| kotlin { | ||
| compilerOptions { | ||
| jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In Android Gradle Kotlin DSL projects, is kotlin { compilerOptions { ... } }valid inside theandroid {} block, or must it be a top-level block? Please check the current Kotlin Gradle plugin / Android Gradle plugin documentation.
💡 Result:
In Android Gradle Kotlin DSL projects, kotlin { compilerOptions { ... } } must be a top-level block, not inside the android {} block. The kotlin {} block is provided by the Kotlin Gradle plugin (e.g., kotlin("android") or kotlin("multiplatform")) and configures Kotlin-specific settings like compiler options at the project extension level. The deprecated android { kotlinOptions { ... } } was inside the android {} block, but the replacement kotlin { compilerOptions { ... } } is top-level, as shown in official Kotlin documentation migration examples for Android projects. With Android Gradle Plugin 9.0+ built-in Kotlin support, the Kotlin plugin may be optional, but when using compilerOptions, the top-level kotlin {} block is still required. Example from Kotlin docs for Android project migration: plugins { id("com.android.application") kotlin("android") } kotlin { compilerOptions { jvmTarget = JvmTarget.fromTarget("17") } } android { // Android config, no kotlinOptions here }
Citations:
- 1: https://kotlinlang.org/docs/gradle-compiler-options.html
- 2: https://developer.android.com/build/migrate-to-built-in-kotlin
🏁 Script executed:
cat -n app/build.gradle.kts | sed -n '50,80p'Repository: GeiserX/CashPilot-android
Length of output: 1129
🏁 Script executed:
cat -n app/build.gradle.kts | head -75Repository: GeiserX/CashPilot-android
Length of output: 3008
Move kotlin { compilerOptions {} } out of android {} block.
The kotlin { compilerOptions {} } block is a top-level extension provided by the Kotlin Gradle plugin and must not be nested inside the android {} block. According to official Kotlin and Android Gradle plugin documentation, this configuration should appear at the project root level. The deprecated android { kotlinOptions {} } was inside the Android block, but its modern replacement requires a top-level kotlin {} block.
Proposed fix
- kotlin {
- compilerOptions {
- jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
- }
- }
-
buildFeatures {
buildConfig = true
compose = true
}
}
+
+kotlin {
+ compilerOptions {
+ jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
+ }
+}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| kotlin { | |
| compilerOptions { | |
| jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) | |
| } | |
| buildFeatures { | |
| buildConfig = true | |
| compose = true | |
| } | |
| } | |
| kotlin { | |
| compilerOptions { | |
| jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/build.gradle.kts` around lines 63 - 66, The kotlin { compilerOptions {
jvmTarget.set(...) } } block is currently nested inside the android { } block;
move this top-level Kotlin Gradle plugin configuration out of android by placing
the kotlin { compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) } } block at the
project/module root (same level as android { } and plugins), removing the nested
version so the Kotlin compilerOptions are defined as a top-level extension
rather than inside the android block.
- Fix dependencyResolution → dependencyResolutionManagement with RepositoriesMode.FAIL_ON_PROJECT_REPOS (matches Pumperly) - Remove keystore from disk after both Gradle steps complete
Kotlin's `in` operator on ConcurrentHashMap maps to containsValue, not containsKey (KT-18053). Use explicit containsKey for correct package lookup.
PACKAGE_USAGE_STATS and READ_NETWORK_USAGE_HISTORY are system permissions accessed via UsageStatsManager after user grants access in settings — lint ProtectedPermissions errors are expected.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
app/build.gradle.kts (1)
63-67:⚠️ Potential issue | 🔴 Critical
kotlin { compilerOptions {} }is still nested underandroid {}.This appears to be the same unresolved placement issue previously reported; keep this as a top-level
kotlin {}block.In Android Gradle Kotlin DSL projects, must `kotlin { compilerOptions { ... } }` be declared at the top level (outside `android {}`), or is nesting it inside `android {}` supported?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/build.gradle.kts` around lines 63 - 67, The kotlin { compilerOptions { jvmTarget.set(...) } } block is incorrectly nested inside android { } — move the entire kotlin { compilerOptions { ... } } declaration to top-level (outside android { }) so the Kotlin DSL compilerOptions configuration applies globally; locate the kotlin block in this file and extract it from within android { } and place it as a standalone top-level kotlin { compilerOptions { jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) } } block.
🧹 Nitpick comments (1)
app/build.gradle.kts (1)
51-54: Consider failing fast whenreleasesigning is absent for release tasks.Current logic silently skips signing; that can allow producing/uploading unsigned release artifacts if CI secrets are missing.
Optional fail-fast guard
val releaseConfig = signingConfigs.findByName("release") - if (releaseConfig?.storeFile != null) { - signingConfig = releaseConfig - } + val isReleaseTask = gradle.startParameter.taskNames.any { it.contains("Release", ignoreCase = true) } + if (releaseConfig?.storeFile != null) { + signingConfig = releaseConfig + } else if (isReleaseTask) { + throw org.gradle.api.GradleException("Release signing config is missing/incomplete.") + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/build.gradle.kts` around lines 51 - 54, The current block silently skips signing when the "release" signingConfig is missing; change it to fail fast for release builds: inspect project.gradle.startParameter.taskNames (or check for common release tasks like "assembleRelease"/"bundleRelease"), and if any release task is requested and signingConfigs.findByName("release")?.storeFile is null, throw a GradleException (or call error(...)) instead of skipping; update the code around signingConfigs.findByName("release"), releaseConfig and signingConfig to enforce this guard so CI will fail when release signing is absent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/build.gradle.kts`:
- Around line 25-39: The current null checks accept empty or whitespace-only
signing inputs (keystorePath, keystorePass, keyAliasValue, keyPass) which can
later break signing; update the guard to treat blank strings as missing by
replacing the null checks with blank-aware checks (e.g., use Kotlin's
isNullOrBlank) so only non-null, non-blank values are used when assigning
storeFile, storePassword, keyAlias and keyPassword.
---
Duplicate comments:
In `@app/build.gradle.kts`:
- Around line 63-67: The kotlin { compilerOptions { jvmTarget.set(...) } } block
is incorrectly nested inside android { } — move the entire kotlin {
compilerOptions { ... } } declaration to top-level (outside android { }) so the
Kotlin DSL compilerOptions configuration applies globally; locate the kotlin
block in this file and extract it from within android { } and place it as a
standalone top-level kotlin { compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) } } block.
---
Nitpick comments:
In `@app/build.gradle.kts`:
- Around line 51-54: The current block silently skips signing when the "release"
signingConfig is missing; change it to fail fast for release builds: inspect
project.gradle.startParameter.taskNames (or check for common release tasks like
"assembleRelease"/"bundleRelease"), and if any release task is requested and
signingConfigs.findByName("release")?.storeFile is null, throw a GradleException
(or call error(...)) instead of skipping; update the code around
signingConfigs.findByName("release"), releaseConfig and signingConfig to enforce
this guard so CI will fail when release signing is absent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 5383f08e-78b4-4499-9e94-9a46f3e5f613
📒 Files selected for processing (2)
app/build.gradle.ktsapp/lint-baseline.xml
✅ Files skipped from review due to trivial changes (1)
- app/lint-baseline.xml
| val keystorePath = findProperty("CASHPILOT_KEYSTORE_PATH") as String? | ||
| ?: System.getenv("CASHPILOT_KEYSTORE_PATH") | ||
| val keystorePass = findProperty("CASHPILOT_KEYSTORE_PASSWORD") as String? | ||
| ?: System.getenv("CASHPILOT_KEYSTORE_PASSWORD") | ||
| val keyAliasValue = findProperty("CASHPILOT_KEY_ALIAS") as String? | ||
| ?: System.getenv("CASHPILOT_KEY_ALIAS") | ||
| val keyPass = findProperty("CASHPILOT_KEY_PASSWORD") as String? | ||
| ?: System.getenv("CASHPILOT_KEY_PASSWORD") | ||
|
|
||
| if (keystorePath != null && keystorePass != null && keyAliasValue != null && keyPass != null) { | ||
| storeFile = file(keystorePath) | ||
| storePassword = keystorePass | ||
| keyAlias = keyAliasValue | ||
| keyPassword = keyPass | ||
| } |
There was a problem hiding this comment.
Treat blank signing inputs as missing configuration.
Using only != null accepts empty strings, which can pass this guard but still break signing later with harder-to-debug errors.
Proposed hardening
- val keystorePath = findProperty("CASHPILOT_KEYSTORE_PATH") as String?
- ?: System.getenv("CASHPILOT_KEYSTORE_PATH")
- val keystorePass = findProperty("CASHPILOT_KEYSTORE_PASSWORD") as String?
- ?: System.getenv("CASHPILOT_KEYSTORE_PASSWORD")
- val keyAliasValue = findProperty("CASHPILOT_KEY_ALIAS") as String?
- ?: System.getenv("CASHPILOT_KEY_ALIAS")
- val keyPass = findProperty("CASHPILOT_KEY_PASSWORD") as String?
- ?: System.getenv("CASHPILOT_KEY_PASSWORD")
+ val keystorePath = ((findProperty("CASHPILOT_KEYSTORE_PATH") as String?)
+ ?: System.getenv("CASHPILOT_KEYSTORE_PATH"))?.takeIf { it.isNotBlank() }
+ val keystorePass = ((findProperty("CASHPILOT_KEYSTORE_PASSWORD") as String?)
+ ?: System.getenv("CASHPILOT_KEYSTORE_PASSWORD"))?.takeIf { it.isNotBlank() }
+ val keyAliasValue = ((findProperty("CASHPILOT_KEY_ALIAS") as String?)
+ ?: System.getenv("CASHPILOT_KEY_ALIAS"))?.takeIf { it.isNotBlank() }
+ val keyPass = ((findProperty("CASHPILOT_KEY_PASSWORD") as String?)
+ ?: System.getenv("CASHPILOT_KEY_PASSWORD"))?.takeIf { it.isNotBlank() }
if (keystorePath != null && keystorePass != null && keyAliasValue != null && keyPass != null) {
storeFile = file(keystorePath)
storePassword = keystorePass
keyAlias = keyAliasValue
keyPassword = keyPass
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/build.gradle.kts` around lines 25 - 39, The current null checks accept
empty or whitespace-only signing inputs (keystorePath, keystorePass,
keyAliasValue, keyPass) which can later break signing; update the guard to treat
blank strings as missing by replacing the null checks with blank-aware checks
(e.g., use Kotlin's isNullOrBlank) so only non-null, non-blank values are used
when assigning storeFile, storePassword, keyAlias and keyPassword.
Summary
Matches the CI/CD setup from Pumperly-android:
ci.yml): builds debug APK + runs lint on every push/PR to main, uploads debug APK as artifactrelease.yml): triggered byv*tags — computes versionCode from tag, decodes keystore from secret, builds signed AAB + APK, creates GitHub Release with artifactsdependabot.yml): weekly Gradle updates (Monday), monthly GitHub Actions updates, grouped minor+patch, security groups, ignores major bumps.gitguardian.yml): suppresses false positives onapp/build.gradle.ktsVERSION_NAME/VERSION_CODEfrom Gradle properties (-Pflags) — defaults to0.1.0/1CASHPILOT_*signing config (reads from Gradle properties or env vars)buildConfig = trueenabledkotlinOptions {}to modernkotlin { compilerOptions {} }DSLSecrets needed for release workflow
Before tagging a release, configure these repo secrets:
KEYSTORE_BASE64— base64-encoded release keystoreKEYSTORE_PASSWORD— keystore passwordKEY_ALIAS— signing key aliasKEY_PASSWORD— signing key passwordTest plan
v0.1.0triggers release workflowSummary by CodeRabbit
Chores
Bug Fixes