From 620ad57e83f8fcc3aab6faec8df156f53ed029e6 Mon Sep 17 00:00:00 2001 From: Darkatse <39111369+Darkatse@users.noreply.github.com> Date: Sun, 26 Jul 2026 01:55:57 -0400 Subject: [PATCH] ci(mobile): allow HTTP in standard release artifacts --- .github/workflows/canary-release.yml | 7 ++ .github/workflows/stable-release.yml | 7 ++ scripts/ci/configure-mobile-http.sh | 86 ++++++++++++++++++++++ tests/testflight-release-workflow.test.mjs | 24 ++++++ 4 files changed, 124 insertions(+) create mode 100755 scripts/ci/configure-mobile-http.sh diff --git a/.github/workflows/canary-release.yml b/.github/workflows/canary-release.yml index 7b9d1ffc..af98e155 100644 --- a/.github/workflows/canary-release.yml +++ b/.github/workflows/canary-release.yml @@ -288,6 +288,9 @@ jobs: printf '%s' "$APPLE_API_KEY_P8" > "$key_path" echo "APPLE_API_KEY_PATH=$key_path" >> "$GITHUB_ENV" + - name: Enable HTTP access for standard mobile bundle + run: ./scripts/ci/configure-mobile-http.sh enable "${{ matrix.mobile }}" + - name: Build mobile bundle uses: tauri-apps/tauri-action@v1 env: @@ -306,6 +309,10 @@ jobs: uploadUpdaterSignatures: false workflowArtifactNamePattern: 'TauriTavern-${{ needs.prepare.outputs.asset_time }}-canary-[platform]-[arch]-[bundle]' + - name: Restore iOS transport security for TestFlight + if: matrix.mobile == 'ios' + run: ./scripts/ci/configure-mobile-http.sh disable ios + - name: Build public TestFlight IPA if: matrix.mobile == 'ios' uses: tauri-apps/tauri-action@v1 diff --git a/.github/workflows/stable-release.yml b/.github/workflows/stable-release.yml index c7e5be01..c9b4d6c5 100644 --- a/.github/workflows/stable-release.yml +++ b/.github/workflows/stable-release.yml @@ -302,6 +302,9 @@ jobs: printf '%s' "$APPLE_API_KEY_P8" > "$key_path" echo "APPLE_API_KEY_PATH=$key_path" >> "$GITHUB_ENV" + - name: Enable HTTP access for standard mobile bundle + run: ./scripts/ci/configure-mobile-http.sh enable "${{ matrix.mobile }}" + - name: Build mobile bundle uses: tauri-apps/tauri-action@v1 env: @@ -320,6 +323,10 @@ jobs: uploadUpdaterSignatures: false workflowArtifactNamePattern: 'TauriTavern-${{ needs.prepare.outputs.version }}-[platform]-[arch]-[bundle]' + - name: Restore iOS transport security for TestFlight + if: matrix.mobile == 'ios' + run: ./scripts/ci/configure-mobile-http.sh disable ios + - name: Build public TestFlight IPA if: matrix.mobile == 'ios' uses: tauri-apps/tauri-action@v1 diff --git a/scripts/ci/configure-mobile-http.sh b/scripts/ci/configure-mobile-http.sh new file mode 100755 index 00000000..59ffcf18 --- /dev/null +++ b/scripts/ci/configure-mobile-http.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +ACTION="${1:-}" +PLATFORM="${2:-}" + +if [[ "${ACTION}" != "enable" && "${ACTION}" != "disable" ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +if [[ "${PLATFORM}" != "android" && "${PLATFORM}" != "ios" ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +replace_once() { + local file="$1" + local from="$2" + local to="$3" + + [[ -f "${file}" ]] || { echo "Missing file: ${file}" >&2; exit 1; } + TT_FROM="${from}" TT_TO="${to}" perl -0pi -e ' + BEGIN { + $from = $ENV{"TT_FROM"}; + $to = $ENV{"TT_TO"}; + } + index($_, $from) >= 0 or die "expected source text not found\n"; + s/\Q$from\E/$to/; + ' "${file}" +} + +contains_block() { + TT_TEXT="$2" perl -0ne 'exit(index($_, $ENV{"TT_TEXT"}) >= 0 ? 0 : 1)' "$1" +} + +toggle_block() { + local file="$1" + local disabled="$2" + local enabled="$3" + + [[ -f "${file}" ]] || { echo "Missing file: ${file}" >&2; exit 1; } + if [[ "${ACTION}" == "enable" ]]; then + contains_block "${file}" "${enabled}" && return + replace_once "${file}" "${disabled}" "${enabled}" + return + fi + + contains_block "${file}" "${enabled}" || return + replace_once "${file}" "${enabled}" "${disabled}" +} + +configure_android() { + local gradle="${ROOT_DIR}/src-tauri/crates/tauritavern/gen/android/app/build.gradle.kts" + local activity="${ROOT_DIR}/src-tauri/crates/tauritavern/gen/android/app/src/main/java/com/tauritavern/client/MainActivity.kt" + + toggle_block \ + "${gradle}" \ + $' getByName("release") {\n' \ + $' getByName("release") {\n manifestPlaceholders["usesCleartextTraffic"] = "true"\n' + toggle_block \ + "${activity}" \ + $' override fun onWebViewCreate(webView: WebView) {\n this.webView = webView\n' \ + $' override fun onWebViewCreate(webView: WebView) {\n this.webView = webView\n webView.settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW\n' + toggle_block \ + "${activity}" \ + $'import android.view.ViewGroup\nimport android.webkit.WebView\n' \ + $'import android.view.ViewGroup\nimport android.webkit.WebSettings\nimport android.webkit.WebView\n' +} + +configure_ios() { + local disabled=$'\tITSAppUsesNonExemptEncryption\n\t\n' + local enabled=$'\tITSAppUsesNonExemptEncryption\n\t\n\tNSAppTransportSecurity\n\t\n\t\tNSAllowsArbitraryLoadsInWebContent\n\t\t\n\t\n' + local file + + for file in \ + "${ROOT_DIR}/src-tauri/crates/tauritavern/Info.ios.plist" \ + "${ROOT_DIR}/src-tauri/crates/tauritavern/gen/apple/tauritavern_iOS/Info.plist"; do + toggle_block "${file}" "${disabled}" "${enabled}" + plutil -lint "${file}" >/dev/null + done +} + +"configure_${PLATFORM}" diff --git a/tests/testflight-release-workflow.test.mjs b/tests/testflight-release-workflow.test.mjs index e50c4563..146ccdb4 100644 --- a/tests/testflight-release-workflow.test.mjs +++ b/tests/testflight-release-workflow.test.mjs @@ -12,6 +12,7 @@ import { const canarySource = readFileSync('.github/workflows/canary-release.yml', 'utf8'); const stableSource = readFileSync('.github/workflows/stable-release.yml', 'utf8'); const testflightSource = readFileSync('.github/workflows/public-testflight.yml', 'utf8'); +const mobileHttpScript = readFileSync('scripts/ci/configure-mobile-http.sh', 'utf8'); const testflightSkill = readFileSync('.github/codex/skills/tauritavern-testflight-notes/SKILL.md', 'utf8'); const humanizerSkill = readFileSync('.github/codex/skills/tauritavern-release-humanizer/SKILL.md', 'utf8'); const canary = YAML.parse(canarySource); @@ -38,6 +39,29 @@ test('Stable and Canary preserve the ordinary IPA and build a separate TestFligh } }); +test('standard mobile artifacts allow HTTP without relaxing the TestFlight IPA', () => { + for (const [name, workflow] of [ + ['Canary', canary], + ['Stable', stable], + ]) { + const steps = workflow.jobs.mobile.steps; + const enableIndex = steps.findIndex((step) => step.name === 'Enable HTTP access for standard mobile bundle'); + const standardIndex = steps.findIndex((step) => step.name === 'Build mobile bundle'); + const restoreIndex = steps.findIndex((step) => step.name === 'Restore iOS transport security for TestFlight'); + const testflightIndex = steps.findIndex((step) => step.name === 'Build public TestFlight IPA'); + + assert.equal(steps[enableIndex].run, './scripts/ci/configure-mobile-http.sh enable "${{ matrix.mobile }}"', name); + assert.equal(steps[restoreIndex].if, "matrix.mobile == 'ios'", name); + assert.equal(steps[restoreIndex].run, './scripts/ci/configure-mobile-http.sh disable ios', name); + assert.equal(enableIndex < standardIndex, true, name); + assert.equal(standardIndex < restoreIndex && restoreIndex < testflightIndex, true, name); + } + + assert.match(mobileHttpScript, /usesCleartextTraffic/u); + assert.match(mobileHttpScript, /MIXED_CONTENT_ALWAYS_ALLOW/u); + assert.match(mobileHttpScript, /NSAllowsArbitraryLoadsInWebContent/u); +}); + test('Stable and Canary target only their existing public TestFlight groups', () => { const canaryJob = canary.jobs.testflight; assert.deepEqual(canaryJob.needs, ['prepare', 'publish']);