Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ jobs:
- name: 🏗 Checkout repository
uses: actions/checkout@v4

- name: 🏗 Select Xcode (Expo SDK 55 requires >=26.0)
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable

- name: 🏗 Setup Node.js
uses: actions/setup-node@v4
with:
Expand Down
14 changes: 13 additions & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ jobs:
- name: Install Dependencies
run: yarn
- name: Update package.json version
id: version
run: |
if [ "${{ github.event_name }}" = "release" ]; then
TAG_NAME="${{ github.event.release.tag_name }}"
Expand All @@ -32,7 +33,18 @@ jobs:
fi
VERSION=${TAG_NAME#v}
npm version $VERSION --no-git-tag-version
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Updated package.json version to $VERSION"

- name: Publish
run: npm publish --access public
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
# *-dynamic releases publish under the `dynamic` dist-tag so they
# don't displace `latest`. Existing static-using clients keep getting
# the static variant; dynamic is opt-in via @dynamic or explicit pin.
if [[ "$VERSION" == *-dynamic* ]]; then
npm publish --access public --tag dynamic
else
npm publish --access public
fi
97 changes: 97 additions & 0 deletions .github/workflows/release-dynamic.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
name: Release Dynamic Variant

# Cuts an opt-in `<version>-dynamic` release that mirrors an existing static
# release tag, but vendors the dynamic-framework variants of AtomicTransact /
# MuppetIOS / QuantumIOS instead of the static ones. Used for clients that hit
# Swift protocol witness thunk corruption (`swift_getAssociatedTypeWitnessSlowImpl`)
# under `use_frameworks! :linkage => :static` when other Swift pods (Sentry,
# Mixpanel, Stripe, etc.) are present in the same app.
#
# The wrapper source code is identical to the static release — the only
# difference is the embedded xcframeworks' Mach-O type. The dynamic release
# commit is created on a detached HEAD and reachable only via the tag; master
# stays static-only.

on:
workflow_dispatch:
inputs:
version:
description: 'Static release version to mirror (e.g., 3.16.0). The base v<version> tag must already exist.'
required: true
type: string

jobs:
release-dynamic:
runs-on: ubuntu-latest
permissions:
contents: write

steps:
- name: Determine versions
id: version
env:
INPUT_VERSION: ${{ inputs.version }}
run: |
STATIC_VERSION="${INPUT_VERSION#v}"
DYNAMIC_VERSION="${STATIC_VERSION}-dynamic"
{
echo "static_version=${STATIC_VERSION}"
echo "dynamic_version=${DYNAMIC_VERSION}"
echo "static_tag=v${STATIC_VERSION}"
echo "dynamic_tag=v${DYNAMIC_VERSION}"
} >> "$GITHUB_OUTPUT"

- name: Checkout static release tag
uses: actions/checkout@v4
with:
ref: v${{ steps.version.outputs.static_version }}
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

- name: Swap to dynamic xcframeworks
env:
STATIC_VERSION: ${{ steps.version.outputs.static_version }}
run: ./scripts/update-ios-sdk.sh --dynamic "$STATIC_VERSION"

- name: Commit and push dynamic-variant tag
env:
DYNAMIC_VERSION: ${{ steps.version.outputs.dynamic_version }}
DYNAMIC_TAG: ${{ steps.version.outputs.dynamic_tag }}
run: |
git add ios/frameworks/
git commit -m "chore: dynamic xcframework variant for ${DYNAMIC_VERSION}"
git tag "$DYNAMIC_TAG"
git push origin "$DYNAMIC_TAG"

- name: Create GitHub release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
STATIC_TAG: ${{ steps.version.outputs.static_tag }}
DYNAMIC_TAG: ${{ steps.version.outputs.dynamic_tag }}
DYNAMIC_VERSION: ${{ steps.version.outputs.dynamic_version }}
run: |
gh release create "$DYNAMIC_TAG" \
--target "$DYNAMIC_TAG" \
--title "v${DYNAMIC_VERSION}" \
--notes "Dynamic xcframework variant of [${STATIC_TAG}](https://github.com/atomicfi/atomic-transact-react-native/releases/tag/${STATIC_TAG}).

Use this variant if your app hits Swift protocol witness thunk corruption (\`swift_getAssociatedTypeWitnessSlowImpl\` / \`EXC_BAD_ACCESS\` at launch) when combining \`@atomicfi/transact-react-native\` with other Swift pods (Sentry, Mixpanel, Stripe, etc.) under \`use_frameworks! :linkage => :static\`.

## Install

\`\`\`bash
# Track the latest dynamic release
npm install @atomicfi/transact-react-native@dynamic

# Or pin explicitly
npm install @atomicfi/transact-react-native@${DYNAMIC_VERSION}
\`\`\`

## What's different

The wrapper source (TypeScript, Swift bridge, Kotlin module) is identical to ${STATIC_TAG}. Only the vendored iOS xcframeworks (\`AtomicTransact\`, \`MuppetIOS\`, \`QuantumIOS\`) are swapped to their dynamic-framework Mach-O builds. This isolates each framework's Swift witness thunks inside its own image instead of letting them participate in app-binary \`linkonce_odr\` dedup, which is what triggers the crash on Xcode 26 / iOS 26."
14 changes: 10 additions & 4 deletions example/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"newArchEnabled": true,
"splash": {
"image": "./assets/splash-icon.png",
"resizeMode": "contain",
Expand All @@ -24,14 +23,21 @@
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"edgeToEdgeEnabled": true,
"package": "com.atomicfi.transact.example"
},
"web": {
"favicon": "./assets/favicon.png"
},
"plugins": [
"expo-dev-client"
"expo-dev-client",
[
"expo-build-properties",
{
"ios": {
"useFrameworks": "static"
}
}
]
],
"developmentClient": {
"silentLaunch": false
Expand All @@ -43,4 +49,4 @@
},
"owner": "atomicfi"
}
}
}
19 changes: 1 addition & 18 deletions example/metro.config.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,11 @@
const { getDefaultConfig } = require('expo/metro-config');
const path = require('path');

// Find the project and workspace directories
const projectRoot = __dirname;
const workspaceRoot = path.resolve(projectRoot, '..');

const config = getDefaultConfig(projectRoot);

// 1. Watch the workspace root for changes
config.watchFolders = [workspaceRoot];

// 2. Let Metro know where to resolve packages from
config.resolver.platforms = ['ios', 'android', 'native', 'web'];

// 3. Force Metro to resolve (sub)dependencies only from the `nodeModulesPath`
config.resolver.disableHierarchicalLookup = true;

// 4. Add the workspace packages to the nodeModulesPaths
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, 'node_modules'),
path.resolve(workspaceRoot, 'node_modules'),
];

// 5. Configure resolver to find workspace packages
config.resolver.resolverMainFields = ['react-native', 'browser', 'main'];
config.watchFolders.push(workspaceRoot);

module.exports = config;
26 changes: 13 additions & 13 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,25 @@
"@atomicfi/transact-react-native": "link:..",
"@react-navigation/native": "^7.1.14",
"@react-navigation/native-stack": "^7.3.21",
"expo": "53.0.20",
"expo-build-properties": "~0.14.8",
"expo-dev-client": "~5.2.4",
"expo-status-bar": "~2.2.3",
"expo-updates": "~0.28.17",
"react": "19.0.0",
"react-native": "0.79.5",
"react-native-safe-area-context": "5.4.0",
"react-native-screens": "^4.11.1"
"expo": "^55.0.0",
"expo-build-properties": "~55.0.13",
"expo-dev-client": "~55.0.28",
"expo-status-bar": "~55.0.5",
"expo-updates": "~55.0.21",
"react": "19.2.0",
"react-native": "0.83.6",
"react-native-safe-area-context": "~5.6.2",
"react-native-screens": "~4.23.0"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@types/react": "~19.0.10",
"@types/react": "~19.2.10",
"eslint": "^9.0.0",
"eslint-config-expo": "~9.2.0",
"eslint-config-expo": "~55.0.0",
"eslint-config-prettier": "^10.1.5",
"eslint-plugin-prettier": "^5.5.1",
"prettier": "^3.6.2",
"typescript": "~5.8.3"
"typescript": "~5.9.2"
},
"private": true
}
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@
"jest": "^29.7.0",
"postcss": "^8.4.0",
"prettier": "^3.0.3",
"react": "19.1.0",
"react-native": "0.80.1",
"react": "19.2.0",
"react-native": "0.83.6",
"react-native-builder-bob": "^0.29.0",
"release-it": "^17.6.0",
"typescript": "^5.2.2",
Expand Down
98 changes: 83 additions & 15 deletions scripts/update-ios-sdk.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,75 @@
set -euo pipefail

# Downloads and vendors AtomicSDK XCFrameworks from GitHub releases.
# Usage: ./scripts/update-ios-sdk.sh <version>
# Example: ./scripts/update-ios-sdk.sh 3.27.2
#
# Usage: ./scripts/update-ios-sdk.sh [--dynamic] <version>
# Examples:
# ./scripts/update-ios-sdk.sh 3.27.2 # static (default)
# ./scripts/update-ios-sdk.sh --dynamic 3.27.2 # dynamic variant
#
# The --dynamic flag swaps the default static xcframeworks for their
# dynamic-framework counterparts (e.g. AtomicTransact-Dynamic.xcframework.tar.gz),
# extracts them under the same on-disk paths the podspec expects (no -Dynamic
# suffix), and stamps `<version>-dynamic` into .sdk-version. Used by the
# release-dynamic workflow to publish opt-in `*-dynamic` releases for clients
# hitting the static-thunk-corruption crash. Wrapper source code is unchanged
# between variants.

DYNAMIC=false
VERSION=""

while [[ $# -gt 0 ]]; do
case "$1" in
--dynamic|-d)
DYNAMIC=true
shift
;;
-h|--help)
echo "Usage: $0 [--dynamic] <version>"
exit 0
;;
-*)
echo "Error: unknown flag '$1'" >&2
echo "Usage: $0 [--dynamic] <version>" >&2
exit 1
;;
*)
if [ -z "$VERSION" ]; then
VERSION="$1"
else
echo "Error: unexpected argument '$1'" >&2
echo "Usage: $0 [--dynamic] <version>" >&2
exit 1
fi
shift
;;
esac
done

VERSION="${1:?Usage: $0 <version>}"
if [ -z "$VERSION" ]; then
echo "Usage: $0 [--dynamic] <version>" >&2
exit 1
fi

# Strip leading 'v' if present
VERSION="${VERSION#v}"

if [ "$DYNAMIC" = true ]; then
TARGET_VERSION="${VERSION}-dynamic"
FRAMEWORKS=(
"AtomicTransact-Dynamic.xcframework.tar.gz"
"MuppetIOS-Dynamic.xcframework.tar.gz"
"QuantumIOS-Dynamic.xcframework.tar.gz"
)
else
TARGET_VERSION="$VERSION"
FRAMEWORKS=(
"AtomicTransact.xcframework.tar.gz"
"MuppetIOS.xcframework.tar.gz"
"QuantumIOS.xcframework.tar.gz"
)
fi

REPO="atomicfi/atomic-transact-ios"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
Expand All @@ -19,17 +80,11 @@ VERSION_FILE="${FRAMEWORKS_DIR}/.sdk-version"

BASE_URL="https://github.com/${REPO}/releases/download/${VERSION}"

FRAMEWORKS=(
"AtomicTransact.xcframework.tar.gz"
"MuppetIOS.xcframework.tar.gz"
"QuantumIOS.xcframework.tar.gz"
)

echo "Updating iOS SDK to version ${VERSION}..."
echo "Updating iOS SDK to version ${TARGET_VERSION}..."

# Check if already at this version
if [ -f "$VERSION_FILE" ] && [ "$(cat "$VERSION_FILE")" = "$VERSION" ]; then
echo "Already at version ${VERSION}. Skipping."
if [ -f "$VERSION_FILE" ] && [ "$(cat "$VERSION_FILE")" = "$TARGET_VERSION" ]; then
echo "Already at version ${TARGET_VERSION}. Skipping."
exit 0
fi

Expand Down Expand Up @@ -57,13 +112,26 @@ for ASSET in "${FRAMEWORKS[@]}"; do
rm -f "${FRAMEWORKS_DIR}/${ASSET}"
done

# Move frameworks out of artifacts/ subdirectory if present
# Move frameworks out of artifacts/ subdirectory if present (release-asset
# packaging is inconsistent — some are tarred with the directory, some without).
if [ -d "${FRAMEWORKS_DIR}/artifacts" ]; then
mv "${FRAMEWORKS_DIR}"/artifacts/*.xcframework "${FRAMEWORKS_DIR}/"
rm -rf "${FRAMEWORKS_DIR}/artifacts"
fi

# In dynamic mode, rename *-Dynamic.xcframework -> *.xcframework so the
# podspec's vendored_frameworks paths don't need to change between variants.
if [ "$DYNAMIC" = true ]; then
for DYN_FW in "${FRAMEWORKS_DIR}"/*-Dynamic.xcframework; do
[ -d "$DYN_FW" ] || continue
BASE_NAME="$(basename "$DYN_FW")"
NEW_NAME="${BASE_NAME%-Dynamic.xcframework}.xcframework"
rm -rf "${FRAMEWORKS_DIR:?}/${NEW_NAME}"
mv "$DYN_FW" "${FRAMEWORKS_DIR}/${NEW_NAME}"
done
fi

# Write version file
echo "$VERSION" > "$VERSION_FILE"
echo "$TARGET_VERSION" > "$VERSION_FILE"

echo "iOS SDK updated to version ${VERSION} successfully."
echo "iOS SDK updated to version ${TARGET_VERSION} successfully."
Loading
Loading