Add signed iOS release workflow
This commit is contained in:
+2
-1
@@ -27,7 +27,8 @@ The source-of-truth layout is:
|
|||||||
- Exercise iOS background/lock, interruption, Bluetooth, route-change, ReplayKit, and iOS 27
|
- Exercise iOS background/lock, interruption, Bluetooth, route-change, ReplayKit, and iOS 27
|
||||||
ScreenCaptureKit paths on devices. Verify distinct left/right capture with the stereo microphone
|
ScreenCaptureKit paths on devices. Verify distinct left/right capture with the stereo microphone
|
||||||
preset, including changing mono/stereo while joined, and Windows desktop/per-app stereo sharing.
|
preset, including changing mono/stereo while joined, and Windows desktop/per-app stereo sharing.
|
||||||
- Complete Developer ID signing/notarization and iOS distribution signing.
|
- Complete Developer ID signing/notarization. The iOS host and ReplayKit extension have been
|
||||||
|
distribution-signed and packaged locally; upload the IPA for Apple's server-side validation.
|
||||||
- Run the published Linux container and a 30-minute-or-longer server soak.
|
- Run the published Linux container and a 30-minute-or-longer server soak.
|
||||||
|
|
||||||
## Working rule
|
## Working rule
|
||||||
|
|||||||
@@ -26,4 +26,11 @@ ReplayKit extension require signing profiles with App Group `group.me.iamtalon.v
|
|||||||
Hardware validation must cover VoiceOver, background and lock behavior, interruptions, route
|
Hardware validation must cover VoiceOver, background and lock behavior, interruptions, route
|
||||||
changes, Bluetooth, ReplayKit, and iOS 27 ScreenCaptureKit audio.
|
changes, Bluetooth, ReplayKit, and iOS 27 ScreenCaptureKit audio.
|
||||||
|
|
||||||
The shared ring contract is documented in `docs/broadcast-ring-format.md`.
|
App Store builds use the same device builder with `--configuration Release`. Set
|
||||||
|
`VOICECAT_BUILD_NUMBER`, `VOICECAT_DISPLAY_VERSION`, `VOICECAT_DEVELOPMENT_TEAM`, the host
|
||||||
|
`VOICECAT_CODESIGN_KEY`/`VOICECAT_CODESIGN_PROVISION` pair, and the extension
|
||||||
|
`VOICECAT_BROADCAST_CODESIGN_KEY`/`VOICECAT_BROADCAST_CODESIGN_PROVISION` pair. The host and
|
||||||
|
extension profiles must both be App Store Connect profiles and include the shared App Group.
|
||||||
|
|
||||||
|
The complete TestFlight workflow is documented in `docs/apple-ios-release.md`. The shared ring
|
||||||
|
contract is documented in `docs/broadcast-ring-format.md`.
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
<plist version="1.0"><dict>
|
<plist version="1.0"><dict>
|
||||||
<key>CFBundleDisplayName</key><string>VoiceCat</string>
|
<key>CFBundleDisplayName</key><string>VoiceCat</string>
|
||||||
<key>CFBundleIdentifier</key><string>me.iamtalon.voicecat</string>
|
<key>CFBundleIdentifier</key><string>me.iamtalon.voicecat</string>
|
||||||
<key>CFBundleShortVersionString</key><string>0.0.1</string>
|
|
||||||
<key>CFBundleVersion</key><string>1</string>
|
|
||||||
<key>LSRequiresIPhoneOS</key><true/>
|
<key>LSRequiresIPhoneOS</key><true/>
|
||||||
<key>NSMicrophoneUsageDescription</key><string>VoiceCat needs microphone access to transmit your voice in channels.</string>
|
<key>NSMicrophoneUsageDescription</key><string>VoiceCat needs microphone access to transmit your voice in channels.</string>
|
||||||
<key>UIBackgroundModes</key><array><string>audio</string></array>
|
<key>UIBackgroundModes</key><array><string>audio</string></array>
|
||||||
|
|||||||
@@ -10,8 +10,19 @@ project="$script_dir/../../../native/apple/broadcast/VoiceCatBroadcast.xcodeproj
|
|||||||
|
|
||||||
mkdir -p "$output"
|
mkdir -p "$output"
|
||||||
signing=()
|
signing=()
|
||||||
|
versioning=(MARKETING_VERSION="${VOICECAT_DISPLAY_VERSION:-0.0.1}")
|
||||||
|
if [[ -n "${VOICECAT_BUILD_NUMBER:-}" ]]; then
|
||||||
|
versioning+=(CURRENT_PROJECT_VERSION="$VOICECAT_BUILD_NUMBER")
|
||||||
|
fi
|
||||||
if [[ "$sdk" == "iphonesimulator" ]]; then
|
if [[ "$sdk" == "iphonesimulator" ]]; then
|
||||||
signing+=(CODE_SIGNING_ALLOWED=NO)
|
signing+=(CODE_SIGNING_ALLOWED=NO)
|
||||||
|
elif [[ -n "${VOICECAT_BROADCAST_CODESIGN_KEY:-}" && -n "${VOICECAT_BROADCAST_CODESIGN_PROVISION:-}" ]]; then
|
||||||
|
signing+=(
|
||||||
|
DEVELOPMENT_TEAM="${VOICECAT_DEVELOPMENT_TEAM:?VOICECAT_DEVELOPMENT_TEAM is required for distribution signing}"
|
||||||
|
CODE_SIGN_STYLE=Manual
|
||||||
|
CODE_SIGN_IDENTITY="$VOICECAT_BROADCAST_CODESIGN_KEY"
|
||||||
|
PROVISIONING_PROFILE_SPECIFIER="$VOICECAT_BROADCAST_CODESIGN_PROVISION"
|
||||||
|
)
|
||||||
elif [[ -n "${VOICECAT_DEVELOPMENT_TEAM:-}" ]]; then
|
elif [[ -n "${VOICECAT_DEVELOPMENT_TEAM:-}" ]]; then
|
||||||
signing+=(DEVELOPMENT_TEAM="$VOICECAT_DEVELOPMENT_TEAM" CODE_SIGN_STYLE=Automatic)
|
signing+=(DEVELOPMENT_TEAM="$VOICECAT_DEVELOPMENT_TEAM" CODE_SIGN_STYLE=Automatic)
|
||||||
if [[ "${VOICECAT_ALLOW_PROVISIONING_UPDATES:-}" == "1" ]]; then
|
if [[ "${VOICECAT_ALLOW_PROVISIONING_UPDATES:-}" == "1" ]]; then
|
||||||
@@ -26,6 +37,7 @@ xcodebuild \
|
|||||||
-arch "$architecture" \
|
-arch "$architecture" \
|
||||||
-derivedDataPath "$output/derived" \
|
-derivedDataPath "$output/derived" \
|
||||||
CONFIGURATION_BUILD_DIR="$output" \
|
CONFIGURATION_BUILD_DIR="$output" \
|
||||||
|
"${versioning[@]}" \
|
||||||
"${signing[@]}" \
|
"${signing[@]}" \
|
||||||
build
|
build
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ while [ "$#" -gt 0 ]; do
|
|||||||
--output) output="$2"; shift 2 ;;
|
--output) output="$2"; shift 2 ;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
echo "usage: $0 [--configuration Debug|Release] [--output DIR]"
|
echo "usage: $0 [--configuration Debug|Release] [--output DIR]"
|
||||||
echo "optional env: VOICECAT_DOTNET, VOICECAT_CODESIGN_KEY, VOICECAT_CODESIGN_PROVISION"
|
echo "optional env: VOICECAT_DOTNET, VOICECAT_BUILD_NUMBER, VOICECAT_DISPLAY_VERSION, VOICECAT_CODESIGN_KEY, VOICECAT_CODESIGN_PROVISION"
|
||||||
|
echo "distribution extension env: VOICECAT_DEVELOPMENT_TEAM, VOICECAT_BROADCAST_CODESIGN_KEY, VOICECAT_BROADCAST_CODESIGN_PROVISION"
|
||||||
exit 0 ;;
|
exit 0 ;;
|
||||||
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
||||||
esac
|
esac
|
||||||
@@ -29,6 +30,7 @@ set -- build "$project" -c "$configuration" -r ios-arm64 --no-restore -m:1 \
|
|||||||
-p:NuGetLockFilePath="$device_lock"
|
-p:NuGetLockFilePath="$device_lock"
|
||||||
if [ -n "${VOICECAT_CODESIGN_KEY:-}" ]; then set -- "$@" -p:CodesignKey="$VOICECAT_CODESIGN_KEY"; fi
|
if [ -n "${VOICECAT_CODESIGN_KEY:-}" ]; then set -- "$@" -p:CodesignKey="$VOICECAT_CODESIGN_KEY"; fi
|
||||||
if [ -n "${VOICECAT_CODESIGN_PROVISION:-}" ]; then set -- "$@" -p:CodesignProvision="$VOICECAT_CODESIGN_PROVISION"; fi
|
if [ -n "${VOICECAT_CODESIGN_PROVISION:-}" ]; then set -- "$@" -p:CodesignProvision="$VOICECAT_CODESIGN_PROVISION"; fi
|
||||||
|
if [ -n "${VOICECAT_BUILD_NUMBER:-}" ]; then set -- "$@" -p:ApplicationVersion="$VOICECAT_BUILD_NUMBER"; fi
|
||||||
"$dotnet_host" "$@"
|
"$dotnet_host" "$@"
|
||||||
|
|
||||||
app="$root/clients/apple/VoiceCat.iOS/bin/$configuration/net10.0-ios27.0/ios-arm64/VoiceCat.iOS.app"
|
app="$root/clients/apple/VoiceCat.iOS/bin/$configuration/net10.0-ios27.0/ios-arm64/VoiceCat.iOS.app"
|
||||||
|
|||||||
@@ -0,0 +1,247 @@
|
|||||||
|
# Releasing the iOS app to TestFlight
|
||||||
|
|
||||||
|
VoiceCat's supported iOS client is the .NET 10 UIKit app in
|
||||||
|
`clients/apple/VoiceCat.iOS`. Its ReplayKit upload extension remains a separate Swift/Xcode
|
||||||
|
target in `native/apple/broadcast`. A release must sign the host and extension separately, with
|
||||||
|
matching versions and the shared App Group entitlement.
|
||||||
|
|
||||||
|
This guide produces an App Store Connect distribution-signed IPA. Uploading it is a separate
|
||||||
|
step that performs Apple's server-side validation.
|
||||||
|
|
||||||
|
## Stable identifiers
|
||||||
|
|
||||||
|
Do not change these when replacing an earlier Swift build with the managed client:
|
||||||
|
|
||||||
|
| Component | Identifier |
|
||||||
|
|---|---|
|
||||||
|
| Apple Developer team | `FJV8L966W4` |
|
||||||
|
| Host app | `me.iamtalon.voicecat` |
|
||||||
|
| ReplayKit extension | `me.iamtalon.voicecat.broadcast` |
|
||||||
|
| Shared App Group | `group.me.iamtalon.voicecat` |
|
||||||
|
|
||||||
|
Provisioning-profile UUIDs are disposable and may change. TestFlight continuity depends on the
|
||||||
|
team, host bundle ID, marketing version, and a build number greater than every build already
|
||||||
|
uploaded for that marketing version. The profile UUID does not identify the App Store app.
|
||||||
|
|
||||||
|
## Secrets and credentials
|
||||||
|
|
||||||
|
An App Store Connect `.p8` API key is not a code-signing key and is not needed to build the IPA.
|
||||||
|
It is only an alternative way to authenticate automated uploads. Keep the `.p8`, Key ID, and
|
||||||
|
Issuer ID private. This workflow uses the distribution private key stored locally in the macOS
|
||||||
|
login Keychain and can upload through Transporter without exposing API credentials.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- The macOS, Xcode, .NET SDK, and iOS workload versions documented in `CLAUDE.md`.
|
||||||
|
- An Apple Distribution certificate with its private key in the login Keychain.
|
||||||
|
- Two current **App Store Connect** distribution provisioning profiles: one for the host and
|
||||||
|
one for the ReplayKit extension.
|
||||||
|
- Both App IDs associated with `group.me.iamtalon.voicecat` in Certificates, Identifiers &
|
||||||
|
Profiles.
|
||||||
|
|
||||||
|
Create or inspect certificates in **Xcode > Settings > Accounts > Manage Certificates**. Verify
|
||||||
|
the resulting local identity from an ordinary Terminal session:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
security find-identity -v -p codesigning
|
||||||
|
```
|
||||||
|
|
||||||
|
The output must include an `Apple Distribution` identity for team `FJV8L966W4`. A certificate
|
||||||
|
visible in the developer portal is insufficient if its private key is not in the Keychain.
|
||||||
|
|
||||||
|
## Create the provisioning profiles
|
||||||
|
|
||||||
|
In [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/profiles/list),
|
||||||
|
create two profiles using **Distribution > App Store Connect**:
|
||||||
|
|
||||||
|
1. `me.iamtalon.voicecat`, with a descriptive name such as `VoiceCatProfile`.
|
||||||
|
2. `me.iamtalon.voicecat.broadcast`, with a name such as `VoiceCatBroadcastProfile`.
|
||||||
|
|
||||||
|
Select the current Apple Distribution certificate for both. Download the `.mobileprovision`
|
||||||
|
files and keep them in a known directory. Opening a profile in Xcode can fail silently when an
|
||||||
|
older Xcode-managed profile is cached, so verify the installed result rather than assuming the
|
||||||
|
double-click worked.
|
||||||
|
|
||||||
|
Each profile must have:
|
||||||
|
|
||||||
|
- the expected explicit application identifier;
|
||||||
|
- `group.me.iamtalon.voicecat` in `com.apple.security.application-groups`;
|
||||||
|
- `get-task-allow=false`;
|
||||||
|
- the same public-certificate fingerprint as the usable local distribution identity.
|
||||||
|
|
||||||
|
If Xcode does not install a downloaded profile, decode it, read its UUID, and install it under
|
||||||
|
that UUID without deleting older profiles:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
profile="$HOME/Downloads/VoiceCatProfile.mobileprovision"
|
||||||
|
decoded="$(mktemp /tmp/voicecat-profile.XXXXXX.plist)"
|
||||||
|
openssl cms -verify -inform DER -in "$profile" -noverify -out "$decoded"
|
||||||
|
uuid="$(/usr/libexec/PlistBuddy -c 'Print :UUID' "$decoded")"
|
||||||
|
mkdir -p "$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles"
|
||||||
|
install -m 0644 "$profile" \
|
||||||
|
"$HOME/Library/Developer/Xcode/UserData/Provisioning Profiles/$uuid.mobileprovision"
|
||||||
|
rm "$decoded"
|
||||||
|
```
|
||||||
|
|
||||||
|
Repeat for the extension profile. Profile names passed to the build are their internal `Name`
|
||||||
|
values, not necessarily their filenames.
|
||||||
|
|
||||||
|
## Build the distribution app
|
||||||
|
|
||||||
|
Choose a monotonically increasing numeric build number. Keep the host and extension marketing
|
||||||
|
and build versions identical.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export VOICECAT_BUILD_NUMBER=2026092101
|
||||||
|
export VOICECAT_DISPLAY_VERSION=0.0.1
|
||||||
|
export VOICECAT_DEVELOPMENT_TEAM=FJV8L966W4
|
||||||
|
|
||||||
|
export VOICECAT_CODESIGN_KEY='Apple Distribution: Your Name (FJV8L966W4)'
|
||||||
|
export VOICECAT_CODESIGN_PROVISION='VoiceCatProfile'
|
||||||
|
|
||||||
|
export VOICECAT_BROADCAST_CODESIGN_KEY="$VOICECAT_CODESIGN_KEY"
|
||||||
|
export VOICECAT_BROADCAST_CODESIGN_PROVISION='VoiceCatBroadcastProfile'
|
||||||
|
|
||||||
|
clients/apple/build-ios-device.sh \
|
||||||
|
--configuration Release \
|
||||||
|
--output dist/ios-appstore
|
||||||
|
```
|
||||||
|
|
||||||
|
The host is built by .NET and the extension by Xcode. The explicit extension variables prevent
|
||||||
|
Xcode from silently selecting a development certificate/profile during a Release build.
|
||||||
|
|
||||||
|
The build output is `dist/ios-appstore/VoiceCat.iOS.app`. Release trimming can report the known
|
||||||
|
aggregate `IL2104` warning for Google.Protobuf; other warnings and all errors remain release
|
||||||
|
blockers.
|
||||||
|
|
||||||
|
### Version metadata and incremental builds
|
||||||
|
|
||||||
|
The host manifest deliberately omits `CFBundleShortVersionString` and `CFBundleVersion` so the
|
||||||
|
.NET `ApplicationDisplayVersion` and `ApplicationVersion` properties own those values. The
|
||||||
|
extension manifest expands Xcode's `MARKETING_VERSION` and `CURRENT_PROJECT_VERSION`.
|
||||||
|
|
||||||
|
After changing this version plumbing or either source manifest, stale iOS intermediates can
|
||||||
|
retain an earlier generated `Info.plist`. Clean the iOS project and rebuild if the packaged
|
||||||
|
values do not match the requested values:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
dotnet clean clients/apple/VoiceCat.iOS/VoiceCat.iOS.csproj \
|
||||||
|
-c Release -r ios-arm64 \
|
||||||
|
-p:NuGetLockFilePath=obj/packages.device.lock.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Closing Finder windows that display the output directory can help if cleanup appears to linger.
|
||||||
|
|
||||||
|
## Validate the app locally
|
||||||
|
|
||||||
|
```bash
|
||||||
|
app=dist/ios-appstore/VoiceCat.iOS.app
|
||||||
|
extension="$app/PlugIns/VoiceCatBroadcast.appex"
|
||||||
|
|
||||||
|
codesign --verify --deep --strict --verbose=4 "$app"
|
||||||
|
codesign -dvvv "$app"
|
||||||
|
codesign -dvvv "$extension"
|
||||||
|
codesign -d --entitlements :- "$app"
|
||||||
|
codesign -d --entitlements :- "$extension"
|
||||||
|
|
||||||
|
/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' \
|
||||||
|
-c 'Print :CFBundleShortVersionString' \
|
||||||
|
-c 'Print :CFBundleVersion' "$app/Info.plist"
|
||||||
|
/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' \
|
||||||
|
-c 'Print :CFBundleShortVersionString' \
|
||||||
|
-c 'Print :CFBundleVersion' "$extension/Info.plist"
|
||||||
|
|
||||||
|
lipo -archs "$app/VoiceCat.iOS"
|
||||||
|
lipo -archs "$extension/VoiceCatBroadcast"
|
||||||
|
```
|
||||||
|
|
||||||
|
Before packaging, confirm:
|
||||||
|
|
||||||
|
- both authorities are the intended Apple Distribution identity;
|
||||||
|
- both team identifiers are `FJV8L966W4`;
|
||||||
|
- both entitlements contain the shared App Group;
|
||||||
|
- both have `get-task-allow=false` and `beta-reports-active=true`;
|
||||||
|
- the bundle identifiers are the stable identifiers above;
|
||||||
|
- both version pairs match; and
|
||||||
|
- both executables contain `arm64`.
|
||||||
|
|
||||||
|
Also decode each embedded profile and verify its name, UUID, application identifier, and
|
||||||
|
`get-task-allow` value:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
for profile in "$app/embedded.mobileprovision" \
|
||||||
|
"$extension/embedded.mobileprovision"; do
|
||||||
|
decoded="$(mktemp /tmp/voicecat-embedded.XXXXXX.plist)"
|
||||||
|
openssl cms -verify -inform DER -in "$profile" -noverify -out "$decoded"
|
||||||
|
/usr/libexec/PlistBuddy \
|
||||||
|
-c 'Print :Name' \
|
||||||
|
-c 'Print :UUID' \
|
||||||
|
-c 'Print :Entitlements:application-identifier' \
|
||||||
|
-c 'Print :Entitlements:get-task-allow' "$decoded"
|
||||||
|
rm "$decoded"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
## Package the IPA
|
||||||
|
|
||||||
|
An IPA is a ZIP archive whose top-level `Payload` directory contains the signed app. Copying the
|
||||||
|
already-signed bundle with `ditto` preserves extended attributes and nested signatures.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
package_root="$(mktemp -d /tmp/voicecat-ipa.XXXXXX)"
|
||||||
|
mkdir "$package_root/Payload"
|
||||||
|
ditto dist/ios-appstore/VoiceCat.iOS.app \
|
||||||
|
"$package_root/Payload/VoiceCat.app"
|
||||||
|
ditto -c -k --sequesterRsrc --keepParent "$package_root/Payload" \
|
||||||
|
"dist/VoiceCat-${VOICECAT_DISPLAY_VERSION}-${VOICECAT_BUILD_NUMBER}.ipa"
|
||||||
|
unzip -t "dist/VoiceCat-${VOICECAT_DISPLAY_VERSION}-${VOICECAT_BUILD_NUMBER}.ipa"
|
||||||
|
shasum -a 256 \
|
||||||
|
"dist/VoiceCat-${VOICECAT_DISPLAY_VERSION}-${VOICECAT_BUILD_NUMBER}.ipa"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Upload to TestFlight
|
||||||
|
|
||||||
|
Open Apple's Transporter app, sign in, drag the IPA into it, and choose **Deliver**. Transporter
|
||||||
|
performs the server-side validation that local `codesign` cannot. Uploading is intentionally not
|
||||||
|
part of the build script because it changes external state and requires App Store Connect
|
||||||
|
authentication.
|
||||||
|
|
||||||
|
After processing completes in App Store Connect, inspect any warnings, complete export-compliance
|
||||||
|
questions, assign the build to an internal testing group, and run the physical-device release
|
||||||
|
gates listed in `PROGRESS.md`.
|
||||||
|
|
||||||
|
## Troubleshooting signatures
|
||||||
|
|
||||||
|
### No signing identities match the profile
|
||||||
|
|
||||||
|
The profile was probably generated for an older distribution certificate. Creating a new
|
||||||
|
certificate does not update existing profiles. Regenerate both App Store Connect profiles with
|
||||||
|
the new certificate, download them, verify their certificate fingerprints, and install them by
|
||||||
|
UUID.
|
||||||
|
|
||||||
|
### `security find-identity` returns zero identities
|
||||||
|
|
||||||
|
Keychain access can be hidden by a sandboxed shell. Retry from ordinary Terminal before changing
|
||||||
|
certificates. If Terminal also reports zero, confirm that the certificate has an associated
|
||||||
|
private key in Keychain Access; a portal certificate or cloud-managed certificate alone cannot
|
||||||
|
sign locally.
|
||||||
|
|
||||||
|
### Automatic provisioning cannot refresh profiles
|
||||||
|
|
||||||
|
Xcode command-line builds can report a missing `Xcode-Token` even when the GUI displays an Apple
|
||||||
|
account. Manual App Store Connect profiles avoid that dependency and make the two-target signing
|
||||||
|
inputs explicit.
|
||||||
|
|
||||||
|
### App and extension versions differ
|
||||||
|
|
||||||
|
Apple validates nested bundle metadata. Confirm the host does not hard-code version keys, the
|
||||||
|
extension uses the Xcode version placeholders, both release environment variables are set, and a
|
||||||
|
clean build regenerated the app manifests.
|
||||||
|
|
||||||
|
## Last verified release build
|
||||||
|
|
||||||
|
On 2026-09-21, version `0.0.1`, build `2026092101` was built with .NET 10.0.401 and Xcode 27.0.
|
||||||
|
The host and ReplayKit extension passed strict nested-signature validation with App Store Connect
|
||||||
|
profiles, matching distribution identities, matching versions, the shared App Group, and
|
||||||
|
`get-task-allow=false`. The resulting IPA was packaged locally; Apple server-side upload
|
||||||
|
validation remains a separate gate.
|
||||||
@@ -17,9 +17,9 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>XPC!</string>
|
<string>XPC!</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>0.0.1</string>
|
<string>$(MARKETING_VERSION)</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
<key>NSExtension</key>
|
<key>NSExtension</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSExtensionPointIdentifier</key>
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict/>
|
<dict>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.me.iamtalon.voicecat</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -53,6 +53,34 @@ public class PublishServerScriptTests
|
|||||||
Assert.DoesNotContain("<TreatWarningsAsErrors>false</TreatWarningsAsErrors>", project);
|
Assert.DoesNotContain("<TreatWarningsAsErrors>false</TreatWarningsAsErrors>", project);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task IosBroadcastExtensionPreservesSharedAppGroupAndSupportsDistributionSigning()
|
||||||
|
{
|
||||||
|
string root = FindRoot();
|
||||||
|
string entitlements = await File.ReadAllTextAsync(Path.Combine(
|
||||||
|
root, "native", "apple", "broadcast", "VoiceCatBroadcast.entitlements"));
|
||||||
|
string script = await File.ReadAllTextAsync(Path.Combine(
|
||||||
|
root, "clients", "apple", "VoiceCat.iOS", "build-broadcast-extension.sh"));
|
||||||
|
|
||||||
|
Assert.Contains("com.apple.security.application-groups", entitlements);
|
||||||
|
Assert.Contains("group.me.iamtalon.voicecat", entitlements);
|
||||||
|
Assert.Contains("CODE_SIGN_STYLE=Manual", script);
|
||||||
|
Assert.Contains("PROVISIONING_PROFILE_SPECIFIER=\"$VOICECAT_BROADCAST_CODESIGN_PROVISION\"", script);
|
||||||
|
Assert.Contains("CURRENT_PROJECT_VERSION=\"$VOICECAT_BUILD_NUMBER\"", script);
|
||||||
|
Assert.Contains("MARKETING_VERSION=\"${VOICECAT_DISPLAY_VERSION:-0.0.1}\"", script);
|
||||||
|
|
||||||
|
string deviceScript = await File.ReadAllTextAsync(Path.Combine(
|
||||||
|
root, "clients", "apple", "build-ios-device.sh"));
|
||||||
|
Assert.Contains("-p:ApplicationVersion=\"$VOICECAT_BUILD_NUMBER\"", deviceScript);
|
||||||
|
|
||||||
|
string appManifest = await File.ReadAllTextAsync(Path.Combine(
|
||||||
|
root, "clients", "apple", "VoiceCat.iOS", "Info.plist"));
|
||||||
|
string extensionManifest = await File.ReadAllTextAsync(Path.Combine(
|
||||||
|
root, "native", "apple", "broadcast", "Info.plist"));
|
||||||
|
Assert.DoesNotContain("<key>CFBundleVersion</key>", appManifest);
|
||||||
|
Assert.Contains("<string>$(CURRENT_PROJECT_VERSION)</string>", extensionManifest);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task DefaultPublishTargetsWindowsAndLinuxWhileRuntimeCanSelectOne()
|
public async Task DefaultPublishTargetsWindowsAndLinuxWhileRuntimeCanSelectOne()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user