-
Notifications
You must be signed in to change notification settings - Fork 1
ci: add CI/CD workflows, dependabot, and release signing #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4e3969c
f45459e
30d1f09
adf3e40
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| paths-ignore: | ||
| - "app/build.gradle.kts" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| version: 2 | ||
|
|
||
| updates: | ||
| - package-ecosystem: "gradle" | ||
| directory: "/" | ||
| schedule: | ||
| interval: "weekly" | ||
| day: "monday" | ||
| open-pull-requests-limit: 10 | ||
| commit-message: | ||
| prefix: "deps" | ||
| labels: | ||
| - "dependencies" | ||
| - "automated" | ||
| groups: | ||
| minor-and-patch: | ||
| patterns: | ||
| - "*" | ||
| update-types: | ||
| - "minor" | ||
| - "patch" | ||
| gradle-security: | ||
| applies-to: security-updates | ||
| patterns: | ||
| - "*" | ||
| ignore: | ||
| - dependency-name: "*" | ||
| update-types: ["version-update:semver-major"] | ||
|
|
||
| - package-ecosystem: "github-actions" | ||
| directory: "/" | ||
| schedule: | ||
| interval: "monthly" | ||
| open-pull-requests-limit: 10 | ||
| commit-message: | ||
| prefix: "ci" | ||
| labels: | ||
| - "ci" | ||
| - "automated" | ||
| groups: | ||
| actions-minor-and-patch: | ||
| patterns: | ||
| - "*" | ||
| update-types: | ||
| - "minor" | ||
| - "patch" | ||
| actions-security: | ||
| applies-to: security-updates | ||
| patterns: | ||
| - "*" | ||
| ignore: | ||
| - dependency-name: "*" | ||
| update-types: ["version-update:semver-major"] |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,34 @@ | ||||||||||||||||||||||
| name: CI | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| on: | ||||||||||||||||||||||
| push: | ||||||||||||||||||||||
| branches: [main] | ||||||||||||||||||||||
| pull_request: | ||||||||||||||||||||||
| branches: [main] | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| jobs: | ||||||||||||||||||||||
| build: | ||||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||||
| steps: | ||||||||||||||||||||||
|
Comment on lines
+9
to
+12
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Set explicit read-only token permissions for CI. This job runs PR code via 🔒 Proposed fix jobs:
build:
runs-on: ubuntu-latest
+ permissions:
+ contents: read
steps:📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| - uses: actions/checkout@v4 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - name: Set up JDK 17 | ||||||||||||||||||||||
| uses: actions/setup-java@v4 | ||||||||||||||||||||||
| with: | ||||||||||||||||||||||
| java-version: '17' | ||||||||||||||||||||||
| distribution: 'temurin' | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - name: Setup Gradle | ||||||||||||||||||||||
| uses: gradle/actions/setup-gradle@v4 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - name: Build debug APK | ||||||||||||||||||||||
| run: ./gradlew assembleDebug | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - name: Run lint | ||||||||||||||||||||||
| run: ./gradlew lintDebug | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - name: Upload debug APK | ||||||||||||||||||||||
| uses: actions/upload-artifact@v4 | ||||||||||||||||||||||
| with: | ||||||||||||||||||||||
| name: app-debug | ||||||||||||||||||||||
| path: app/build/outputs/apk/debug/app-debug.apk | ||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| name: Release | ||
|
|
||
| on: | ||
| push: | ||
| tags: | ||
| - 'v*' | ||
|
|
||
| jobs: | ||
| release: | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: write | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - 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" | ||
|
Comment on lines
+17
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Validate the tag before deriving
💡 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 |
||
|
|
||
| - name: Set up JDK 17 | ||
| uses: actions/setup-java@v4 | ||
| with: | ||
| java-version: '17' | ||
| distribution: 'temurin' | ||
|
|
||
| - name: Setup Gradle | ||
| uses: gradle/actions/setup-gradle@v4 | ||
|
|
||
| - name: Decode keystore | ||
| run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > app/release.keystore | ||
|
|
||
| - name: Build release AAB | ||
| run: ./gradlew bundleRelease -PVERSION_NAME=${{ steps.version.outputs.name }} -PVERSION_CODE=${{ steps.version.outputs.code }} | ||
| env: | ||
| CASHPILOT_KEYSTORE_PATH: release.keystore | ||
| CASHPILOT_KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} | ||
| CASHPILOT_KEY_ALIAS: ${{ secrets.KEY_ALIAS }} | ||
| CASHPILOT_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} | ||
|
|
||
| - name: Build release APK | ||
| run: ./gradlew assembleRelease -PVERSION_NAME=${{ steps.version.outputs.name }} -PVERSION_CODE=${{ steps.version.outputs.code }} | ||
| env: | ||
| CASHPILOT_KEYSTORE_PATH: release.keystore | ||
| CASHPILOT_KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }} | ||
| CASHPILOT_KEY_ALIAS: ${{ secrets.KEY_ALIAS }} | ||
| CASHPILOT_KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }} | ||
|
|
||
| - name: Remove keystore | ||
| if: always() | ||
| run: rm -f app/release.keystore | ||
|
|
||
| - name: Create GitHub Release | ||
| uses: softprops/action-gh-release@v2 | ||
| with: | ||
| tag_name: ${{ steps.version.outputs.tag }} | ||
| name: ${{ steps.version.outputs.tag }} | ||
| generate_release_notes: true | ||
| files: | | ||
| app/build/outputs/bundle/release/app-release.aab | ||
| app/build/outputs/apk/release/app-release.apk | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -13,8 +13,31 @@ android { | |||||||||||||||||||||||||||||||
| applicationId = "com.cashpilot.android" | ||||||||||||||||||||||||||||||||
| minSdk = 26 // Android 8.0 — NotificationListenerService, NetworkStatsManager | ||||||||||||||||||||||||||||||||
| targetSdk = 35 | ||||||||||||||||||||||||||||||||
| versionCode = 1 | ||||||||||||||||||||||||||||||||
| versionName = "0.1.0" | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| val ciVersionName = findProperty("VERSION_NAME") as String? ?: "0.1.0" | ||||||||||||||||||||||||||||||||
| val ciVersionCode = (findProperty("VERSION_CODE") as String?)?.toIntOrNull() ?: 1 | ||||||||||||||||||||||||||||||||
| versionCode = ciVersionCode | ||||||||||||||||||||||||||||||||
| versionName = ciVersionName | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| signingConfigs { | ||||||||||||||||||||||||||||||||
| create("release") { | ||||||||||||||||||||||||||||||||
| 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 | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
Comment on lines
+25
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Treat blank signing inputs as missing configuration. Using only 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 |
||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| buildTypes { | ||||||||||||||||||||||||||||||||
|
|
@@ -25,6 +48,10 @@ android { | |||||||||||||||||||||||||||||||
| getDefaultProguardFile("proguard-android-optimize.txt"), | ||||||||||||||||||||||||||||||||
| "proguard-rules.pro" | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| val releaseConfig = signingConfigs.findByName("release") | ||||||||||||||||||||||||||||||||
| if (releaseConfig?.storeFile != null) { | ||||||||||||||||||||||||||||||||
| signingConfig = releaseConfig | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
@@ -33,11 +60,18 @@ android { | |||||||||||||||||||||||||||||||
| targetCompatibility = JavaVersion.VERSION_17 | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| kotlinOptions { | ||||||||||||||||||||||||||||||||
| jvmTarget = "17" | ||||||||||||||||||||||||||||||||
| kotlin { | ||||||||||||||||||||||||||||||||
| compilerOptions { | ||||||||||||||||||||||||||||||||
| jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
Comment on lines
+63
to
+66
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 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:
🏁 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 The 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
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| lint { | ||||||||||||||||||||||||||||||||
| baseline = file("lint-baseline.xml") | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| buildFeatures { | ||||||||||||||||||||||||||||||||
| buildConfig = true | ||||||||||||||||||||||||||||||||
| compose = true | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <issues format="6" by="lint" type="baseline" client="gradle" generated="This file is automatically generated by lint; do not edit"> | ||
|
|
||
| <issue | ||
| id="ProtectedPermissions" | ||
| message="Permission is only granted to system apps" | ||
| errorLine1=' <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"' | ||
| errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> | ||
| <location | ||
| file="src/main/AndroidManifest.xml" | ||
| line="10" | ||
| column="22"/> | ||
| </issue> | ||
|
|
||
| <issue | ||
| id="ProtectedPermissions" | ||
| message="Permission is only granted to system apps" | ||
| errorLine1=' <uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />' | ||
| errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> | ||
| <location | ||
| file="src/main/AndroidManifest.xml" | ||
| line="12" | ||
| column="22"/> | ||
| </issue> | ||
|
|
||
| </issues> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't exclude the whole Gradle script from secret scanning.
app/build.gradle.ktsnow 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