diff --git a/.claude/commands/release.md b/.claude/commands/release.md new file mode 100644 index 000000000..9b61ae465 --- /dev/null +++ b/.claude/commands/release.md @@ -0,0 +1,174 @@ +--- +description: "Create a new release: bump version, create PR, build mainnet, publish draft release" +argument_hint: "[--critical]" +allowed_tools: Bash, Read, Edit, Write, Glob, Grep, AskUserQuestion, mcp__github__create_pull_request, mcp__github__list_pull_requests, mcp__github__pull_request_read, mcp__github__get_file_contents, mcp__github__update_pull_request +--- + +Automate the full release process for bitkit-android. + +**Examples:** +- `/release` - Interactive, prompts for version (defaults to patch bump) +- `/release --critical` - Same, but marks the release as critical in release.json + +## Steps + +### 1. Read Current Version + +Read `app/build.gradle.kts` and extract: +- `versionCode` (integer, e.g. `176`) +- `versionName` (string, e.g. `"2.0.2"`) + +Parse versionName into `{major}.{minor}.{patch}` components. + +Compute defaults: +- Next patch: `{major}.{minor}.{patch+1}` +- Next minor: `{major}.{minor+1}.0` +- Next major: `{major+1}.0.0` +- Next versionCode: `versionCode + 1` + +### 2. Parse Arguments + +From `$ARGUMENTS`: +- `--critical`: Set `critical: true` in `.github/release.json` (default: `false`) + +### 3. Ask for Version + +Use `AskUserQuestion` with header "Version": + +**Question:** `"New version? (current: {versionName}, build {versionCode})"` + +**Options:** +1. `{major}.{minor}.{patch+1}` (Recommended) — description: "Patch release" +2. `{major}.{minor+1}.0` — description: "Minor release" +3. `{major+1}.0.0` — description: "Major release" + +The user can always pick "Other" to enter a custom version string. + +Store the chosen version as `newVersionName` and compute `newVersionCode = versionCode + 1`. + +### 4. Create Release Branch & Bump Version + +```bash +git checkout master +git pull origin master +git checkout -b release/{newVersionCode} +``` + +Edit `app/build.gradle.kts`: +- Change `versionCode = {old}` to `versionCode = {newVersionCode}` +- Change `versionName = "{old}"` to `versionName = "{newVersionName}"` + +```bash +git add app/build.gradle.kts +git commit -m "chore: version {newVersionName}" +git push -u origin release/{newVersionCode} +``` + +### 5. Create Version Bump PR + +Read `.github/pull_request_template.md` for structure. Create PR: + +- **Title:** `chore: bump version {newVersionName}` +- **Base:** master +- **Body:** +``` +Bump version to {newVersionName} (build {newVersionCode}) for release. + +### Description + +- `versionCode`: {oldVersionCode} → {newVersionCode} +- `versionName`: {oldVersionName} → {newVersionName} + +### Preview + +N/A + +### QA Notes + +N/A +``` + +Store the PR URL for the summary. + +### 6. Build Mainnet Release + +```bash +./gradlew assembleMainnetRelease +``` + +Expected APK path: `app/build/outputs/apk/mainnet/release/bitkit-mainnet-release-{newVersionCode}-universal.apk` + +Verify the file exists. If the build fails, stop and report the error to the user. + +### 7. Update `.github/release.json` + +Get current UTC timestamp: `date -u +"%Y-%m-%dT%H:%M:%SZ"` + +Write `.github/release.json`: +```json +{ + "platforms": { + "android": { + "version": "v{newVersionName}", + "buildNumber": {newVersionCode}, + "notes": "https://github.com/synonymdev/bitkit/releases/tag/v{newVersionName}", + "pub_date": "{UTC timestamp}", + "url": "https://play.google.com/store/apps/details?id=to.bitkit", + "critical": {true if --critical flag, false otherwise} + } + } +} +``` + +Commit and push: +```bash +git add .github/release.json +git commit -m "chore: update release.json" +git push +``` + +### 8. Tag & Push + +Determine the previous version tag for changelog generation: `v{oldVersionName}`. + +```bash +git tag -a v{newVersionName} -m "v{newVersionName}" +git push origin v{newVersionName} +``` + +### 9. Create Draft GitHub Release + +```bash +gh release create v{newVersionName} \ + --title "v{newVersionName}" \ + --draft \ + --generate-notes \ + --notes-start-tag v{oldVersionName} +``` + +### 10. Upload APK to Draft Release + +```bash +gh release upload v{newVersionName} \ + app/build/outputs/apk/mainnet/release/bitkit-mainnet-release-{newVersionCode}-universal.apk +``` + +### 11. Return to Master + +```bash +git checkout master +``` + +### 12. Output Summary + +``` +Release v{newVersionName} (build {newVersionCode}) + +Version bump PR: {PR URL} +Release branch: release/{newVersionCode} +Tag: v{newVersionName} +Draft release: {release URL} +APK uploaded: bitkit-mainnet-release-{newVersionCode}-universal.apk + +Next: publish the draft release on GitHub when ready. +``` diff --git a/.github/release.json b/.github/release.json new file mode 100644 index 000000000..f1f8e0f74 --- /dev/null +++ b/.github/release.json @@ -0,0 +1,13 @@ +{ + "platforms": { + "ios": null, + "android": { + "version": "v2.0.2", + "buildNumber": 176, + "notes": "https://github.com/synonymdev/bitkit/releases/tag/v2.0.2", + "pub_date": "2026-02-16T23:20:07Z", + "url": "https://play.google.com/store/apps/details?id=to.bitkit", + "critical": false + } + } +} diff --git a/app/src/main/java/to/bitkit/data/dto/AppUpdaterDTO.kt b/app/src/main/java/to/bitkit/data/dto/AppUpdaterDTO.kt index 161413a9f..9c8707337 100644 --- a/app/src/main/java/to/bitkit/data/dto/AppUpdaterDTO.kt +++ b/app/src/main/java/to/bitkit/data/dto/AppUpdaterDTO.kt @@ -14,7 +14,7 @@ data class ReleaseInfoDTO( @Serializable data class Platforms( val android: PlatformDetails, - val ios: PlatformDetails?, + val ios: PlatformDetails? = null, ) /** diff --git a/app/src/main/java/to/bitkit/env/Env.kt b/app/src/main/java/to/bitkit/env/Env.kt index c4436dfd1..48aeedcf9 100644 --- a/app/src/main/java/to/bitkit/env/Env.kt +++ b/app/src/main/java/to/bitkit/env/Env.kt @@ -5,6 +5,7 @@ import org.lightningdevkit.ldknode.LogLevel import org.lightningdevkit.ldknode.Network import org.lightningdevkit.ldknode.PeerDetails import to.bitkit.BuildConfig +import to.bitkit.BuildConfig.VERSION_NAME import to.bitkit.ext.ensureDir import to.bitkit.ext.of import to.bitkit.models.BlocktankNotificationType @@ -22,7 +23,7 @@ internal object Env { val locales = BuildConfig.LOCALES.split(",") const val walletSyncIntervalSecs = 10_uL val platform = "Android ${Build.VERSION.RELEASE} (API ${Build.VERSION.SDK_INT})" - const val version = "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})" + const val version = "$VERSION_NAME (${BuildConfig.VERSION_CODE})" val ldkLogLevel = LogLevel.TRACE @@ -127,7 +128,8 @@ internal object Env { const val APP_STORE_URL = "https://apps.apple.com/app/bitkit-wallet/id6502440655" const val PLAY_STORE_URL = "https://play.google.com/store/apps/details?id=to.bitkit" - const val RELEASE_URL = "https://github.com/synonymdev/bitkit-android/releases/download/updater/release.json" + const val REPO_URL = "https://github.com/synonymdev/bitkit-android" + const val RELEASE_URL = "$REPO_URL/releases/download/updater/release.json" const val EXCHANGES_URL = "https://bitcoin.org/en/exchanges#international" const val BTC_MAP_URL = "https://btcmap.org/map" const val BITKIT_WEBSITE = "https://bitkit.to/" diff --git a/app/src/main/java/to/bitkit/services/AppUpdaterService.kt b/app/src/main/java/to/bitkit/services/AppUpdaterService.kt index eedf28331..6fd48c2f3 100644 --- a/app/src/main/java/to/bitkit/services/AppUpdaterService.kt +++ b/app/src/main/java/to/bitkit/services/AppUpdaterService.kt @@ -30,7 +30,7 @@ class AppUpdaterService @Inject constructor( } else -> throw AppUpdaterError.InvalidResponse( - "Failed to fetch release info: ${response.status.description}" + "Failed to fetch release info: ${response.status}" ) } } diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties new file mode 100644 index 000000000..500e90918 --- /dev/null +++ b/gradle/gradle-daemon-jvm.properties @@ -0,0 +1,13 @@ +#This file is generated by updateDaemonJvm +toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/29ee363f71d060405f729a8f1b7f7aef/redirect +toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/ecd23fd7707c683afbcd6052998cb6a9/redirect +toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/29ee363f71d060405f729a8f1b7f7aef/redirect +toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/ecd23fd7707c683afbcd6052998cb6a9/redirect +toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/10fc3bf1ee0001078a473afe6e43cfdb/redirect +toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/9c55677aff3966382f3d853c0959bfb2/redirect +toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/29ee363f71d060405f729a8f1b7f7aef/redirect +toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/ecd23fd7707c683afbcd6052998cb6a9/redirect +toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/248ffb1098f61659502d0c09aa348294/redirect +toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/ac151d55def6b6a9a159dc4cb4642851/redirect +toolchainVendor=JETBRAINS +toolchainVersion=21 diff --git a/settings.gradle.kts b/settings.gradle.kts index 538bf73e8..0f208580d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -27,6 +27,9 @@ pluginManagement { gradlePluginPortal() } } +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} @Suppress("UnstableApiUsage") dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)