From 44bc3f927ee950daa7fe4e245d099581e1226644 Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Thu, 8 Oct 2026 18:35:59 +0000 Subject: [PATCH] address review: guard the release pipeline and harden the metadata test - fail the release before uploading to Play when the versionCode has no changelog - rely on supply's defaults for release_status and metadata_path - enforce 8-bit PNGs and reject tRNS chunks in MetadataConstraintsTest - name the nightly artifact's APKs distinctly - fix doc drift and the stale libraries uniqueId --- .github/workflows/build-nightly.yml | 10 ++++-- .github/workflows/release.yml | 4 +-- app/config/libraries/subtracks.json | 2 +- .../metadata/MetadataConstraintsTest.kt | 31 ++++++++++++++++++- signing/README.md | 2 +- 5 files changed, 42 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-nightly.yml b/.github/workflows/build-nightly.yml index 6af1ddc8..db1a09bb 100644 --- a/.github/workflows/build-nightly.yml +++ b/.github/workflows/build-nightly.yml @@ -20,9 +20,15 @@ jobs: - uses: ./.github/actions/build with: gradle-args: ":app:assembleDebug :app:assembleNightly --no-daemon" + - name: Collect + run: | + cp app/build/outputs/apk/debug/app-debug.apk subtracks-nightly-debug.apk + cp app/build/outputs/apk/nightly/app-nightly.apk subtracks-nightly.apk - name: Checksums - run: sha256sum app/build/outputs/apk/debug/app-debug.apk app/build/outputs/apk/nightly/app-nightly.apk + run: sha256sum subtracks-nightly.apk subtracks-nightly-debug.apk - uses: actions/upload-artifact@v7 with: name: subtracks-nightly - path: app/build/outputs/apk + path: | + subtracks-nightly.apk + subtracks-nightly-debug.apk diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0fd1636c..31d450fa 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -68,6 +68,8 @@ jobs: else echo "value=false" >> "$GITHUB_OUTPUT" fi + - name: Check release notes exist + run: test -f "metadata/en-US/changelogs/${{ needs.build.outputs.version-code }}.txt" - name: Upload to Google Play if: steps.stable.outputs.value == 'true' env: @@ -79,9 +81,7 @@ jobs: --aab dist/bundle/release/app-release.aab \ --package_name com.subtracks \ --track "${{ vars.PLAY_TRACK || 'internal' }}" \ - --release_status completed \ --json_key "$RUNNER_TEMP/play.json" \ - --metadata_path metadata \ --mapping dist/mapping/release/mapping.txt - name: Remove Play service account if: always() diff --git a/app/config/libraries/subtracks.json b/app/config/libraries/subtracks.json index 26f3802b..dbfcdf7b 100644 --- a/app/config/libraries/subtracks.json +++ b/app/config/libraries/subtracks.json @@ -1,5 +1,5 @@ { - "uniqueId": "com.subtracks:next", + "uniqueId": "com.subtracks", "name": "Subtracks", "description": "Subtracks itself, a client for Subsonic-compatible servers.", "website": "https://github.com/austinried/subtracks", diff --git a/app/src/test/kotlin/com/subtracks/metadata/MetadataConstraintsTest.kt b/app/src/test/kotlin/com/subtracks/metadata/MetadataConstraintsTest.kt index 33a53d8d..9599ea33 100644 --- a/app/src/test/kotlin/com/subtracks/metadata/MetadataConstraintsTest.kt +++ b/app/src/test/kotlin/com/subtracks/metadata/MetadataConstraintsTest.kt @@ -66,6 +66,7 @@ class MetadataConstraintsTest { RGBA_COLOR_TYPE, image.colorType, ) + assertTrue("${file.path} must be an 8-bit PNG; bitDepth ${image.bitDepth}", image.bitDepth == 8) assertTrue("${file.path} must be at most 1 MiB", file.length() <= 1024 * 1024) } } @@ -119,6 +120,8 @@ class MetadataConstraintsTest { RGB_COLOR_TYPE, image.colorType, ) + assertTrue("${file.path} must be an 8-bit PNG; bitDepth ${image.bitDepth}", image.bitDepth == 8) + assertTrue("${file.path} must not carry a tRNS (transparency) chunk", !image.hasTrns) } ImageFormat.JPEG -> { @@ -156,6 +159,8 @@ class MetadataConstraintsTest { val width: Int, val height: Int, val colorType: Int?, + val bitDepth: Int?, + val hasTrns: Boolean, val format: ImageFormat, ) @@ -166,12 +171,33 @@ class MetadataConstraintsTest { width = bytes.beInt(PNG_WIDTH_OFFSET), height = bytes.beInt(PNG_HEIGHT_OFFSET), colorType = bytes[PNG_COLOR_TYPE_OFFSET].toInt() and 0xFF, + bitDepth = bytes[PNG_BIT_DEPTH_OFFSET].toInt() and 0xFF, + hasTrns = bytes.hasTrnsChunk(), format = ImageFormat.PNG, ) } val image = ImageIO.read(file) assertTrue("unreadable image ${file.path}", image != null) - return ImageInfo(image.width, image.height, colorType = null, format = if (bytes.isJpeg()) ImageFormat.JPEG else ImageFormat.OTHER) + return ImageInfo( + image.width, + image.height, + null, + null, + hasTrns = false, + format = if (bytes.isJpeg()) ImageFormat.JPEG else ImageFormat.OTHER, + ) + } + + private fun ByteArray.hasTrnsChunk(): Boolean { + var offset = PNG_SIGNATURE_SIZE + while (offset + 8 <= size) { + val length = beInt(offset) + val type = String(this, offset + 4, 4, Charsets.US_ASCII) + if (type == "tRNS") return true + if (type == "IEND" || length < 0) return false + offset += PNG_CHUNK_OVERHEAD + length + } + return false } private fun ByteArray.isPng(): Boolean = @@ -208,6 +234,9 @@ class MetadataConstraintsTest { const val PNG_WIDTH_OFFSET = 16 const val PNG_HEIGHT_OFFSET = 20 + const val PNG_BIT_DEPTH_OFFSET = 24 const val PNG_COLOR_TYPE_OFFSET = 25 + const val PNG_SIGNATURE_SIZE = 8 + const val PNG_CHUNK_OVERHEAD = 12 } } diff --git a/signing/README.md b/signing/README.md index 81b630b1..aef4382d 100644 --- a/signing/README.md +++ b/signing/README.md @@ -41,7 +41,7 @@ keytool -printcert -jarfile app-release.aab ## `nightly` builds -The signing key is the committed [`app/debug.keystore`](../app/debug.keystore) (a public key: alias `androiddebugkey`, password `android`). Certificate SHA-256: +The signing key is the committed [`app/debug.keystore`](../app/debug.keystore) (a public key: alias `androiddebugkey`, password `android`). The `subtracks-nightly` artifact contains `subtracks-nightly.apk` (minified) and `subtracks-nightly-debug.apk` (debuggable), both `com.subtracks.nightly`. Certificate SHA-256: ``` # keytool format