ci(mobile): allow HTTP in standard release artifacts

This commit is contained in:
Darkatse 2026-07-26 01:55:57 -04:00
parent 96fec267b0
commit 620ad57e83
4 changed files with 124 additions and 0 deletions

View file

@ -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

View file

@ -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

View file

@ -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 <enable|disable> <android|ios>" >&2
exit 1
fi
if [[ "${PLATFORM}" != "android" && "${PLATFORM}" != "ios" ]]; then
echo "Usage: $0 <enable|disable> <android|ios>" >&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=$'\t<key>ITSAppUsesNonExemptEncryption</key>\n\t<false/>\n'
local enabled=$'\t<key>ITSAppUsesNonExemptEncryption</key>\n\t<false/>\n\t<key>NSAppTransportSecurity</key>\n\t<dict>\n\t\t<key>NSAllowsArbitraryLoadsInWebContent</key>\n\t\t<true/>\n\t</dict>\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}"

View file

@ -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']);