From a17a5b8be4be59b821277320aa1ab0e2176a8b0c Mon Sep 17 00:00:00 2001 From: kcw-grunt Date: Mon, 17 Aug 2026 12:10:07 +0100 Subject: [PATCH 1/3] chore: remove CircleCI, rely on Xcode Cloud for CI CircleCI's macOS executor costs had gotten too high. Xcode Cloud (ci_scripts/) already runs the build/test pipeline, so it's now the sole CI for this repo. - Delete .circleci/ (per-push unit tests, daily-develop maintenance job, and UI-test/weekly-maintenance job) - Remove setup_circle_ci from fastlane/Fastfile's before_all - Move the Linux-only i18n translation-coverage check (scripts/test_i18n_coverage.py) to a new GitHub Actions workflow (.github/workflows/i18n-coverage.yml) since it has no Xcode Cloud equivalent - Drop the CircleCI status badge and the CircleCI-updated Gist tests-badge from README; update the Testing section to reference Xcode Cloud + the new i18n workflow - Clean up stale .circleci references in ci_scripts/ci_post_clone.sh and .github/workflows/pr-summary.yml Co-Authored-By: grunt-claude-bot <308083480+grunt-claude-bot@users.noreply.github.com> Co-Authored-By: kcw-grunt --- .circleci/build-uitest-config.yml | 236 ----------------------- .circleci/config.yml | 282 ---------------------------- .circleci/daily-develop-config.yml | 237 ----------------------- .github/workflows/i18n-coverage.yml | 45 +++++ .github/workflows/pr-summary.yml | 10 +- README.md | 5 +- ci_scripts/ci_post_clone.sh | 3 +- fastlane/Fastfile | 1 - scripts/test_i18n_coverage.py | 2 +- 9 files changed, 53 insertions(+), 768 deletions(-) delete mode 100644 .circleci/build-uitest-config.yml delete mode 100644 .circleci/config.yml delete mode 100644 .circleci/daily-develop-config.yml create mode 100644 .github/workflows/i18n-coverage.yml diff --git a/.circleci/build-uitest-config.yml b/.circleci/build-uitest-config.yml deleted file mode 100644 index 1f5eeada..00000000 --- a/.circleci/build-uitest-config.yml +++ /dev/null @@ -1,236 +0,0 @@ -# .circleci/config.yml -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/2.0/configuration-reference -# Inspired by: https://medium.com/uptech-team/swift-package-manager-and-how-to-cache-it-with-ci-14968cd58c5f -version: 2.1 -orbs: - macos: circleci/macos@2 - ruby: circleci/ruby@2.5.3 -jobs: - build-uitest: - macos: - xcode: 16.4.0 - resource_class: macos.m1.medium.gen1 - working_directory: ~/ios/ - environment: - HOMEBREW_NO_AUTO_UPDATE: 1 - FASTLANE_SKIP_UPDATE_CHECK: 1 - FL_OUTPUT_DIR: output - FASTLANE_LANE: run_ui_iphone16promax_tests - XCODE_SCHEME: brainwalletUITests - XCODE_PROJECT: brainwallet.xcodeproj - FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT: 120 - FASTLANE_XCODEBUILD_SETTINGS_RETRIES: 3 - shell: /bin/bash --login -o pipefail - steps: - - add_ssh_keys: - fingerprints: - - "SHA256:Z4bsDPFhTv8vuTERTBcs6b5RHFRspyI77b7tQUsju1g" - - checkout - - # Restore all caches - - restore_cache: - name: Restore caches - keys: - - combined-cache-v1-{{ arch }}-{{ checksum "Gemfile" }}-{{ checksum "Package.resolved" }}-{{ checksum "brainwallet.xcodeproj/project.pbxproj" }} - - combined-cache-v1-{{ arch }}-{{ checksum "Gemfile" }}-{{ checksum "Package.resolved" }}- - - combined-cache-v1-{{ arch }}-{{ checksum "Gemfile" }}- - - combined-cache-v1-{{ arch }}- - - # Setup Ruby, clean environment, and install dependencies - - run: - name: Setup Ruby environment and dependencies - command: | - # Install Ruby with rbenv - export PATH="$HOME/.rbenv/bin:$PATH" - if ! command -v rbenv &> /dev/null; then - brew update - brew install rbenv ruby-build - fi - eval "$(rbenv init -)" - rbenv install -s 3.3.0 - rbenv global 3.3.0 - ruby -v - - # Clean gem environment - rm -f Gemfile.lock - rm -rf vendor/bundle - gem uninstall fastlane -aIx || true - gem uninstall scan -aIx || true - gem cleanup - - # Install Bundler and dependencies - gem install bundler:2.6.9 - bundle config set --local path 'vendor/bundle' - bundle install --jobs=4 --retry=3 - - # Setup SSH and initialize submodules - - run: - name: Setup SSH and submodules - command: | - echo 'github.com ssh-rsa SHA256:Z4bsDPFhTv8vuTERTBcs6b5RHFRspyI77b7tQUsju1g' >> ~/.ssh/known_hosts - ssh-add -D - ssh-add ~/.ssh/id_rsa_* - git submodule sync --recursive - git submodule update --init --recursive --jobs=4 --filter=blob:none -- ':(exclude)Private/ios-games' - git submodule init && git submodule update --init Modules/core - - # Setup environment files (MUST be before build) - - run: - name: Setup environment files - command: | - # Export environment variables - echo 'export GOOGLE_SERVICES_PLIST="$GOOGLE_SERVICES_PLIST"' >> $BASH_ENV - echo 'export REMOTE_CONFIG_DEFAULTS="$REMOTE_CONFIG_DEFAULTS"' >> $BASH_ENV - echo 'export DEBUG_SERVICE_DATA="$DEBUG_SERVICE_DATA"' >> $BASH_ENV - - # Create environment files in root directory (required by Xcode build) - echo "$GOOGLE_SERVICES_PLIST" | base64 --decode > GoogleService-Info.plist - echo "$REMOTE_CONFIG_DEFAULTS" | base64 --decode > remote-config-defaults.plist - echo "$DEBUG_SERVICE_DATA" | base64 --decode > service-data.plist - - # Fail fast on a bad env var here instead of a cryptic - # "unable to read input file as a property list" from xcodebuild's - # CopyPlistFile step later. Naming the broken var/file points - # straight at what to fix in CircleCI's project env vars. - check_plist() { - if [ ! -s "$2" ] || ! /usr/libexec/PlistBuddy -c "Print" "$2" >/dev/null 2>&1; then - echo "ERROR: $1 did not decode to a valid plist -- check its value in CircleCI's project env vars" - exit 1 - fi - } - check_plist "GOOGLE_SERVICES_PLIST -> GoogleService-Info.plist" GoogleService-Info.plist - check_plist "REMOTE_CONFIG_DEFAULTS -> remote-config-defaults.plist" remote-config-defaults.plist - check_plist "DEBUG_SERVICE_DATA -> service-data.plist" service-data.plist - - echo "GoogleService-Info.plist created successfully:" - ls -la GoogleService-Info.plist - echo "File size: $(wc -c < GoogleService-Info.plist) bytes" - # Do NOT print file contents here -- this repo is public and - # CircleCI build logs for public GitHub repos are viewable - # without a CircleCI login by default, so dumping any lines of a - # decoded secret (this file's API_KEY, GOOGLE_APP_ID, etc.) would - # leak it. check_plist above already validates the content is a - # real plist without printing it; that's the right way to verify - # this, not head/cat. - - # Also copy to brainwallet directory if needed - mkdir -p ./brainwallet - cp GoogleService-Info.plist ./brainwallet/PreLaunchResources/ - cp remote-config-defaults.plist ./brainwallet/PreLaunchResources/ - cp service-data.plist ./brainwallet/PreLaunchResources/ - - # Verify copies were successful - echo "Files in brainwallet directory:" - ls -la ./brainwallet/ - - # Prepare build environment - - run: - name: Prepare build environment - command: | - # Clean derived data - rm -rf ~/Library/Developer/Xcode/DerivedData - rm -rf ./DerivedData - - # Boot simulator - DEVICE_ID=$(xcrun simctl list devices | grep "iPhone 16 Pro Max" | grep -v "unavailable" | head -1 | sed 's/.*(//' | sed 's/).*//') - if [ -n "$DEVICE_ID" ]; then - echo "Booting iPhone 16 Pro Max simulator..." - xcrun simctl boot "$DEVICE_ID" || echo "Device may already be booted" - sleep 10 - else - echo "iPhone 16 Pro Max not found, using available device" - xcrun simctl list devices - fi - - run: - name: Verify build prerequisites - command: | - pwd - echo "Verifying GoogleService-Info.plist is available for xcodebuild:" - - # Check root directory - if [ -f "GoogleService-Info.plist" ]; then - echo "✅ GoogleService-Info.plist found in root directory" - ls -la GoogleService-Info.plist - else - echo "❌ GoogleService-Info.plist NOT found in root directory" - fi - - # Check brainwallet subdirectory - if [ -f "./brainwallet/GoogleService-Info.plist" ]; then - echo "✅ GoogleService-Info.plist found in brainwallet directory" - ls -la ./brainwallet/GoogleService-Info.plist - else - echo "❌ GoogleService-Info.plist NOT found in brainwallet directory" - fi - - # Find all plist files for debugging - echo "All .plist files in project:" - find . -name "*.plist" -type f - - # Ensure at least one GoogleService-Info.plist exists - if [ ! -f "GoogleService-Info.plist" ] && [ ! -f "./brainwallet/GoogleService-Info.plist" ]; then - echo "ERROR: GoogleService-Info.plist not found in any expected location!" - exit 1 - fi - - echo "✅ All build prerequisites verified" - - run: - name: Run Fastlane iPhone 16 simulator Tests - command: | - export PATH="$HOME/.rbenv/bin:$PATH" - eval "$(rbenv init -)" - bundle exec fastlane $FASTLANE_LANE - - save_cache: - name: Save combined cache - key: combined-cache-v1-{{ arch }}-{{ checksum "Gemfile" }}-{{ checksum "Package.resolved" }}-{{ checksum "brainwallet.xcodeproj/project.pbxproj" }} - paths: - - ~/.rbenv - - vendor/bundle - - ~/.gem - - SourcePackages/ - - ~/Library/Caches/org.swift.swiftpm/ - - ~/Library/org.swift.swiftpm/ - - ./DerivedData - - ~/Library/Developer/Xcode/DerivedData - - ~/Library/Caches/com.apple.dt.Xcode/ - - # Store artifacts and handle failures - - run: - name: Capture crash logs - command: | - find ~/Library/Logs/DiagnosticReports -name "*Brainwallet*" -type f -exec echo "Found crash log: {}" \; -exec cat {} \; || echo "No crash logs found" - find ~/Library/Developer/CoreSimulator/Devices/*/data/Library/Logs/CrashReporter/ -name "*Brainwallet*" -type f -exec echo "Found simulator crash log: {}" \; -exec cat {} \; || echo "No simulator crash logs found" - log show --predicate 'process == "Brainwallet"' --last 10m || echo "No system logs found for Brainwallet" - when: on_fail - - - store_artifacts: - path: output - - store_test_results: - path: output/scan - - store_artifacts: - path: ~/Library/Logs/DiagnosticReports - destination: crash-logs - when: on_fail -workflows: - version: 4 - # UI test workflow - only runs when manually triggered or on specific branches - build-uitest-brainwallet-ios: - jobs: - - build-uitest: - context: - - bw-ios-test - # Weekly scheduled workflow UITest - weekly-uittest-maintenance-workflow: - triggers: - - schedule: - # Run daily at 6:00 AM UTC (adjust for your timezone) - cron: "0 6 * * *" - filters: - branches: - only: - - develop - jobs: - - weekly-uittest-maintenance: - context: - - circleci-agents \ No newline at end of file diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 8b5117d4..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,282 +0,0 @@ -# .circleci/config.yml -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/2.0/configuration-reference -# Inspired by: https://medium.com/uptech-team/swift-package-manager-and-how-to-cache-it-with-ci-14968cd58c5f -version: 2.1 -orbs: - macos: circleci/macos@2 - ruby: circleci/ruby@2.5.3 -jobs: - build-unit-tests-per-push: - macos: - xcode: 16.4.0 - resource_class: macos.m1.medium.gen1 - working_directory: ~/ios/ - environment: - HOMEBREW_NO_AUTO_UPDATE: 1 - FASTLANE_SKIP_UPDATE_CHECK: 1 - FL_OUTPUT_DIR: output - FASTLANE_LANE: run_unit_tests_iPhone16ProMax - XCODE_SCHEME: brainwalletUnitTests - XCODE_PROJECT: brainwallet.xcodeproj - FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT: 120 - FASTLANE_XCODEBUILD_SETTINGS_RETRIES: 3 - shell: /bin/bash --login -o pipefail - steps: - - add_ssh_keys: - fingerprints: - - "SHA256:Z4bsDPFhTv8vuTERTBcs6b5RHFRspyI77b7tQUsju1g" - - checkout - - # Restore all caches - - restore_cache: - name: Restore caches - keys: - - combined-cache-v1-{{ arch }}-{{ checksum "Gemfile" }}-{{ checksum "Package.resolved" }}-{{ checksum "brainwallet.xcodeproj/project.pbxproj" }} - - combined-cache-v1-{{ arch }}-{{ checksum "Gemfile" }}-{{ checksum "Package.resolved" }}- - - combined-cache-v1-{{ arch }}-{{ checksum "Gemfile" }}- - - combined-cache-v1-{{ arch }}- - - # Setup Ruby, clean environment, and install dependencies - - run: - name: Setup Ruby environment and dependencies - command: | - # Install Ruby with rbenv - export PATH="$HOME/.rbenv/bin:$PATH" - if ! command -v rbenv &> /dev/null; then - brew update - brew install rbenv ruby-build - fi - eval "$(rbenv init -)" - rbenv install -s 3.3.0 - rbenv global 3.3.0 - ruby -v - - # Clean gem environment - rm -f Gemfile.lock - rm -rf vendor/bundle - gem uninstall fastlane -aIx || true - gem uninstall scan -aIx || true - gem cleanup - - # Install Bundler and dependencies - gem install bundler:2.6.9 - bundle config set --local path 'vendor/bundle' - bundle install --jobs=4 --retry=3 - # Setup SSH & GH Token and initialize submodules - - run: - name: Configure Github Auth & Setup SSH and submodules - command: | - echo 'github.com ssh-rsa SHA256:Z4bsDPFhTv8vuTERTBcs6b5RHFRspyI77b7tQUsju1g' >> ~/.ssh/known_hosts - ssh-add ~/.ssh/id_rsa_* - sed -i "s#https://github.com/#https://${GH_TOKEN}@github.com/#g" .gitmodules - git submodule init && git submodule update --init Modules/core && git submodule update --init Private/general-purpose - - run: - name: Setup environment files - command: | - # Export environment variables - echo 'export BRAINWALLET_IOS_STOREKIT_V1_0_FILE="$BRAINWALLET_IOS_STOREKIT_V1_0_FILE"' >> $BASH_ENV - echo 'export GOOGLE_SERVICES_PLIST="$GOOGLE_SERVICES_PLIST"' >> $BASH_ENV - echo 'export REMOTE_CONFIG_DEFAULTS="$REMOTE_CONFIG_DEFAULTS"' >> $BASH_ENV - echo 'export DEBUG_SERVICE_DATA="$DEBUG_SERVICE_DATA"' >> $BASH_ENV - - # Create environment files in root directory (required by Xcode build) - echo "$BRAINWALLET_IOS_STOREKIT_V1_0_FILE" | base64 --decode > Brainwallet-StoreKit-v1.storekit - echo "$GOOGLE_SERVICES_PLIST" | base64 --decode > GoogleService-Info.plist - echo "$REMOTE_CONFIG_DEFAULTS" | base64 --decode > remote-config-defaults.plist - echo "$DEBUG_SERVICE_DATA" | base64 --decode > service-data.plist - - # Fail fast on a bad env var here instead of a cryptic - # "unable to read input file as a property list" from xcodebuild's - # CopyPlistFile step later (this exact failure mode hit Xcode - # Cloud build 431: DEBUG_SERVICE_DATA didn't decode to a valid - # plist). Naming the broken var/file points straight at what to - # fix in the CircleCI project env vars. - check_plist() { - if [ ! -s "$2" ] || ! /usr/libexec/PlistBuddy -c "Print" "$2" >/dev/null 2>&1; then - echo "ERROR: $1 did not decode to a valid plist -- check its value in CircleCI's project env vars" - exit 1 - fi - } - check_plist "GOOGLE_SERVICES_PLIST -> GoogleService-Info.plist" GoogleService-Info.plist - check_plist "REMOTE_CONFIG_DEFAULTS -> remote-config-defaults.plist" remote-config-defaults.plist - check_plist "DEBUG_SERVICE_DATA -> service-data.plist" service-data.plist - # Brainwallet-StoreKit-v1.storekit is JSON (Xcode's StoreKit - # Configuration format), not a plist -- plutil -lint rejects JSON - # outright, so validate with a real JSON parser instead. - if [ ! -s "Brainwallet-StoreKit-v1.storekit" ] || ! python3 -m json.tool Brainwallet-StoreKit-v1.storekit >/dev/null 2>&1; then - echo "ERROR: BRAINWALLET_IOS_STOREKIT_V1_0_FILE -> Brainwallet-StoreKit-v1.storekit did not decode to valid JSON -- check its value in CircleCI's project env vars" - exit 1 - fi - - echo "GoogleService-Info.plist created successfully:" - ls -la GoogleService-Info.plist - echo "File size: $(wc -c < GoogleService-Info.plist) bytes" - # Do NOT print file contents here -- this repo is public and - # CircleCI build logs for public GitHub repos are viewable - # without a CircleCI login by default, so dumping any lines of a - # decoded secret (this file's API_KEY, GOOGLE_APP_ID, etc.) would - # leak it. check_plist above already validates the content is a - # real plist without printing it; that's the right way to verify - # this, not head/cat. - - # Also copy to brainwallet directory if needed - mkdir -p ./brainwallet - cp Brainwallet-StoreKit-v1.storekit ./brainwallet/PreLaunchResources/ - cp GoogleService-Info.plist ./brainwallet/PreLaunchResources/ - cp remote-config-defaults.plist ./brainwallet/PreLaunchResources/ - cp service-data.plist ./brainwallet/PreLaunchResources/ - - # Verify copies were successful - echo "Files in brainwallet directory:" - ls -la ./brainwallet/ - - # Prepare build environment - - run: - name: Prepare build environment - command: | - # Clean derived data - rm -rf ~/Library/Developer/Xcode/DerivedData - rm -rf ./DerivedData - - # Boot simulator - DEVICE_ID=$(xcrun simctl list devices | grep "iPhone 16 Pro Max" | grep -v "unavailable" | head -1 | sed 's/.*(//' | sed 's/).*//') - if [ -n "$DEVICE_ID" ]; then - echo "Booting iPhone 16 Pro Max simulator..." - xcrun simctl boot "$DEVICE_ID" || echo "Device may already be booted" - sleep 10 - else - echo "iPhone 16 Pro Max not found, using available device" - xcrun simctl list devices - fi - - run: - name: Verify build prerequisites - command: | - pwd - echo "Verifying GoogleService-Info.plist is available for xcodebuild:" - - # Check root directory - if [ -f "GoogleService-Info.plist" ]; then - echo "✅ GoogleService-Info.plist found in root directory" - ls -la GoogleService-Info.plist - else - echo "❌ GoogleService-Info.plist NOT found in root directory" - fi - - # Check brainwallet subdirectory - if [ -f "./brainwallet/GoogleService-Info.plist" ]; then - echo "✅ GoogleService-Info.plist found in brainwallet directory" - ls -la ./brainwallet/GoogleService-Info.plist - else - echo "❌ GoogleService-Info.plist NOT found in brainwallet directory" - fi - - # Find all plist files for debugging - echo "All .plist files in project:" - find . -name "*.plist" -type f - - # Ensure at least one GoogleService-Info.plist exists - if [ ! -f "GoogleService-Info.plist" ] && [ ! -f "./brainwallet/GoogleService-Info.plist" ]; then - echo "ERROR: GoogleService-Info.plist not found in any expected location!" - exit 1 - fi - - echo "✅ All build prerequisites verified" - # ── i18n coverage check ──────────────────────────────────────────────────── - # Lightweight informational job: runs on Linux (no Mac machine needed), - # reports translation coverage per language, and fails the pipeline only - # if any language drops below COVERAGE_WARN_PCT (default 80 %). - # It never blocks a release for in-flight translations — raise the threshold - # once the auto-translate workflow has caught up. - - run: - name: Check i18n coverage - command: python3 scripts/test_i18n_coverage.py - - - run: - name: Run Fastlane iPhone 16 simulator Tests - command: | - export PATH="$HOME/.rbenv/bin:$PATH" - eval "$(rbenv init -)" - bundle exec fastlane $FASTLANE_LANE - - save_cache: - name: Save combined cache - key: combined-cache-v1-{{ arch }}-{{ checksum "Gemfile" }}-{{ checksum "Package.resolved" }}-{{ checksum "brainwallet.xcodeproj/project.pbxproj" }} - paths: - - ~/.rbenv - - vendor/bundle - - ~/.gem - - SourcePackages/ - - ~/Library/Caches/org.swift.swiftpm/ - - ~/Library/org.swift.swiftpm/ - - ./DerivedData - - ~/Library/Developer/Xcode/DerivedData - - ~/Library/Caches/com.apple.dt.Xcode/ - # Store artifacts and handle failures - - run: - name: Capture crash logs - command: | - find ~/Library/Logs/DiagnosticReports -name "*Brainwallet*" -type f -exec echo "Found crash log: {}" \; -exec cat {} \; || echo "No crash logs found" - find ~/Library/Developer/CoreSimulator/Devices/*/data/Library/Logs/CrashReporter/ -name "*Brainwallet*" -type f -exec echo "Found simulator crash log: {}" \; -exec cat {} \; || echo "No simulator crash logs found" - log show --predicate 'process == "Brainwallet"' --last 10m || echo "No system logs found for Brainwallet" - when: on_fail - - store_test_results: - path: test_output - - store_artifacts: - path: ~/Library/Logs/DiagnosticReports - destination: crash-logs - when: on_fail - - run: - name: "Update tests-passed badge (shields.io gist endpoint)" - # Only the primary branch's run should own the badge state - a feature - # branch with fewer/broken tests shouldn't flap the README badge. - # Requires GIST_TOKEN (a PAT with only the `gist` scope) and GIST_ID - # to be set as CircleCI project env vars. - when: always - command: | - if [ "$CIRCLE_BRANCH" != "develop" ]; then - echo "Not on develop, skipping badge update." - exit 0 - fi - if [ -z "$GIST_TOKEN" ] || [ -z "$GIST_ID" ]; then - echo "GIST_TOKEN/GIST_ID not set, skipping badge update." - exit 0 - fi - - f="test_output/report.junit" - total=0 - failures=0 - skipped=0 - if [ -f "$f" ]; then - total=$(grep -o 'tests="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*') - failures=$(grep -o 'failures="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*') - skipped=$(grep -o 'skipped="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*') - fi - passed=$(( ${total:-0} - ${failures:-0} - ${skipped:-0} )) - - if [ "${failures:-0}" -gt 0 ]; then - message="${passed} passed, ${failures} failed" - color="red" - else - message="${passed} passed" - color="brightgreen" - fi - - payload=$(printf '{"files":{"tests-badge.json":{"content":"{\\"schemaVersion\\":1,\\"label\\":\\"tests\\",\\"message\\":\\"%s\\",\\"color\\":\\"%s\\"}"}}}' "$message" "$color") - - curl -s -X PATCH \ - -H "Authorization: token ${GIST_TOKEN}" \ - -H "Accept: application/vnd.github+json" \ - "https://api.github.com/gists/${GIST_ID}" \ - -d "$payload" > /dev/null - - echo "Badge updated: ${message}" - -workflows: - version: 4 - - # Main workflow - runs unit tests on every push - build-test-brainwallet-ios: - jobs: - - build-unit-tests-per-push: - context: - - bw-ios-test diff --git a/.circleci/daily-develop-config.yml b/.circleci/daily-develop-config.yml deleted file mode 100644 index 621e596a..00000000 --- a/.circleci/daily-develop-config.yml +++ /dev/null @@ -1,237 +0,0 @@ -# .circleci/config.yml -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/2.0/configuration-reference -# Inspired by: https://medium.com/uptech-team/swift-package-manager-and-how-to-cache-it-with-ci-14968cd58c5f -version: 2.1 -orbs: - macos: circleci/macos@2 - ruby: circleci/ruby@2.5.3 -jobs: - # Daily maintenance job on branch: develop - daily-maintenance: - macos: - xcode: 16.4.0 - resource_class: macos.m1.medium.gen1 - working_directory: ~/ios/ - environment: - HOMEBREW_NO_AUTO_UPDATE: 1 - FASTLANE_SKIP_UPDATE_CHECK: 1 - FL_OUTPUT_DIR: output - FASTLANE_LANE: run_all_tests_iPhone16ProMax - XCODE_SCHEME: brainwalletUnitTests - XCODE_PROJECT: brainwallet.xcodeproj - FASTLANE_XCODEBUILD_SETTINGS_TIMEOUT: 120 - FASTLANE_XCODEBUILD_SETTINGS_RETRIES: 3 - shell: /bin/bash --login -o pipefail - steps: - - add_ssh_keys: - fingerprints: - - "SHA256:Z4bsDPFhTv8vuTERTBcs6b5RHFRspyI77b7tQUsju1g" - - checkout - - # Restore all caches - - restore_cache: - name: Restore caches - keys: - - combined-cache-v1-{{ arch }}-{{ checksum "Gemfile" }}-{{ checksum "Package.resolved" }}-{{ checksum "brainwallet.xcodeproj/project.pbxproj" }} - - combined-cache-v1-{{ arch }}-{{ checksum "Gemfile" }}-{{ checksum "Package.resolved" }}- - - combined-cache-v1-{{ arch }}-{{ checksum "Gemfile" }}- - - combined-cache-v1-{{ arch }}- - - # Setup Ruby, clean environment, and install dependencies - - run: - name: Setup Ruby environment and dependencies - command: | - # Install Ruby with rbenv - export PATH="$HOME/.rbenv/bin:$PATH" - if ! command -v rbenv &> /dev/null; then - brew update - brew install rbenv ruby-build - fi - eval "$(rbenv init -)" - rbenv install -s 3.3.0 - rbenv global 3.3.0 - ruby -v - - # Clean gem environment - rm -f Gemfile.lock - rm -rf vendor/bundle - gem uninstall fastlane -aIx || true - gem uninstall scan -aIx || true - gem cleanup - - # Install Bundler and dependencies - gem install bundler:2.6.9 - bundle config set --local path 'vendor/bundle' - bundle install --jobs=4 --retry=3 - # Setup SSH & GH Token and initialize submodules - - run: - name: Configure Github Auth & Setup SSH and submodules - command: | - echo 'github.com ssh-rsa SHA256:Z4bsDPFhTv8vuTERTBcs6b5RHFRspyI77b7tQUsju1g' >> ~/.ssh/known_hosts - ssh-add -D - ssh-add ~/.ssh/id_rsa_* - sed -i "s#https://github.com/#https://${GH_TOKEN}@github.com/#g" .gitmodules - git submodule init && git submodule update --init Modules/core && git submodule update --init Private/general-purpose - # Setup environment files (MUST be before build) - - run: - name: Setup environment files - command: | - # Export environment variables - echo 'export BRAINWALLET_IOS_STOREKIT_V1_0_FILE="$BRAINWALLET_IOS_STOREKIT_V1_0_FILE"' >> $BASH_ENV - echo 'export GOOGLE_SERVICES_PLIST="$GOOGLE_SERVICES_PLIST"' >> $BASH_ENV - echo 'export REMOTE_CONFIG_DEFAULTS="$REMOTE_CONFIG_DEFAULTS"' >> $BASH_ENV - echo 'export DEBUG_SERVICE_DATA="$DEBUG_SERVICE_DATA"' >> $BASH_ENV - - # Create environment files in root directory (required by Xcode build) - echo "$BRAINWALLET_IOS_STOREKIT_V1_0_FILE" | base64 --decode > Brainwallet-StoreKit-v1.storekit - echo "$GOOGLE_SERVICES_PLIST" | base64 --decode > GoogleService-Info.plist - echo "$REMOTE_CONFIG_DEFAULTS" | base64 --decode > remote-config-defaults.plist - echo "$DEBUG_SERVICE_DATA" | base64 --decode > service-data.plist - - # Fail fast on a bad env var here instead of a cryptic - # "unable to read input file as a property list" from xcodebuild's - # CopyPlistFile step later. Naming the broken var/file points - # straight at what to fix in CircleCI's project env vars. - check_plist() { - if [ ! -s "$2" ] || ! /usr/libexec/PlistBuddy -c "Print" "$2" >/dev/null 2>&1; then - echo "ERROR: $1 did not decode to a valid plist -- check its value in CircleCI's project env vars" - exit 1 - fi - } - check_plist "GOOGLE_SERVICES_PLIST -> GoogleService-Info.plist" GoogleService-Info.plist - check_plist "REMOTE_CONFIG_DEFAULTS -> remote-config-defaults.plist" remote-config-defaults.plist - check_plist "DEBUG_SERVICE_DATA -> service-data.plist" service-data.plist - # Brainwallet-StoreKit-v1.storekit is JSON (Xcode's StoreKit - # Configuration format), not a plist -- plutil -lint rejects JSON - # outright, so validate with a real JSON parser instead. - if [ ! -s "Brainwallet-StoreKit-v1.storekit" ] || ! python3 -m json.tool Brainwallet-StoreKit-v1.storekit >/dev/null 2>&1; then - echo "ERROR: BRAINWALLET_IOS_STOREKIT_V1_0_FILE -> Brainwallet-StoreKit-v1.storekit did not decode to valid JSON -- check its value in CircleCI's project env vars" - exit 1 - fi - - echo "GoogleService-Info.plist created successfully:" - ls -la GoogleService-Info.plist - echo "File size: $(wc -c < GoogleService-Info.plist) bytes" - # Do NOT print file contents here -- this repo is public and - # CircleCI build logs for public GitHub repos are viewable - # without a CircleCI login by default, so dumping any lines of a - # decoded secret (this file's API_KEY, GOOGLE_APP_ID, etc.) would - # leak it. check_plist above already validates the content is a - # real plist without printing it; that's the right way to verify - # this, not head/cat. - - # Also copy to brainwallet directory if needed - mkdir -p ./brainwallet - cp Brainwallet-StoreKit-v1.storekit ./brainwallet/PreLaunchResources/ - cp GoogleService-Info.plist ./brainwallet/PreLaunchResources/ - cp remote-config-defaults.plist ./brainwallet/PreLaunchResources/ - cp service-data.plist ./brainwallet/PreLaunchResources/ - - # Verify copies were successful - echo "Files in brainwallet directory:" - ls -la ./brainwallet/ - - # Prepare build environment - - run: - name: Prepare build environment - command: | - # Clean derived data - rm -rf ~/Library/Developer/Xcode/DerivedData - rm -rf ./DerivedData - - # Boot simulator - DEVICE_ID=$(xcrun simctl list devices | grep "iPhone 16 Pro Max" | grep -v "unavailable" | head -1 | sed 's/.*(//' | sed 's/).*//') - if [ -n "$DEVICE_ID" ]; then - echo "Booting iPhone 16 Pro Max simulator..." - xcrun simctl boot "$DEVICE_ID" || echo "Device may already be booted" - sleep 10 - else - echo "iPhone 16 Pro Max not found, using available device" - xcrun simctl list devices - fi - - run: - name: Verify build prerequisites - command: | - pwd - echo "Verifying GoogleService-Info.plist is available for xcodebuild:" - - # Check root directory - if [ -f "GoogleService-Info.plist" ]; then - echo "✅ GoogleService-Info.plist found in root directory" - ls -la GoogleService-Info.plist - else - echo "❌ GoogleService-Info.plist NOT found in root directory" - fi - - # Check brainwallet subdirectory - if [ -f "./brainwallet/GoogleService-Info.plist" ]; then - echo "✅ GoogleService-Info.plist found in brainwallet directory" - ls -la ./brainwallet/GoogleService-Info.plist - else - echo "❌ GoogleService-Info.plist NOT found in brainwallet directory" - fi - - # Find all plist files for debugging - echo "All .plist files in project:" - find . -name "*.plist" -type f - - # Ensure at least one GoogleService-Info.plist exists - if [ ! -f "GoogleService-Info.plist" ] && [ ! -f "./brainwallet/GoogleService-Info.plist" ]; then - echo "ERROR: GoogleService-Info.plist not found in any expected location!" - exit 1 - fi - - echo "✅ All build prerequisites verified" - - run: - name: Run Fastlane iPhone 16 simulator Tests - command: | - export PATH="$HOME/.rbenv/bin:$PATH" - eval "$(rbenv init -)" - bundle exec fastlane $FASTLANE_LANE - - save_cache: - name: Save combined cache - key: combined-cache-v1-{{ arch }}-{{ checksum "Gemfile" }}-{{ checksum "Package.resolved" }}-{{ checksum "brainwallet.xcodeproj/project.pbxproj" }} - paths: - - ~/.rbenv - - vendor/bundle - - ~/.gem - - SourcePackages/ - - ~/Library/Caches/org.swift.swiftpm/ - - ~/Library/org.swift.swiftpm/ - - ./DerivedData - - ~/Library/Developer/Xcode/DerivedData - - ~/Library/Caches/com.apple.dt.Xcode/ - - # Store artifacts and handle failures - - run: - name: Capture crash logs - command: | - find ~/Library/Logs/DiagnosticReports -name "*Brainwallet*" -type f -exec echo "Found crash log: {}" \; -exec cat {} \; || echo "No crash logs found" - find ~/Library/Developer/CoreSimulator/Devices/*/data/Library/Logs/CrashReporter/ -name "*Brainwallet*" -type f -exec echo "Found simulator crash log: {}" \; -exec cat {} \; || echo "No simulator crash logs found" - log show --predicate 'process == "Brainwallet"' --last 10m || echo "No system logs found for Brainwallet" - when: on_fail - - - store_artifacts: - path: output - - store_test_results: - path: output/scan - - store_artifacts: - path: ~/Library/Logs/DiagnosticReports - destination: crash-logs - when: on_fail -workflows: - version: 4 - daily-maintenance-workflow: - triggers: - - schedule: - # Run daily at 6:00 AM UTC (adjust for your timezone) - cron: "0 6 * * *" - filters: - branches: - only: - - develop - jobs: - - daily-maintenance: - context: - - circleci-agents diff --git a/.github/workflows/i18n-coverage.yml b/.github/workflows/i18n-coverage.yml new file mode 100644 index 00000000..b4d9a7b5 --- /dev/null +++ b/.github/workflows/i18n-coverage.yml @@ -0,0 +1,45 @@ +name: 🌐 i18n Coverage Check + +on: + push: + branches: + - main + - develop + paths: + - '**/Localizable.xcstrings' + - 'scripts/test_i18n_coverage.py' + pull_request: + branches: + - main + - develop + paths: + - '**/Localizable.xcstrings' + - 'scripts/test_i18n_coverage.py' + workflow_dispatch: + +permissions: + contents: read + +jobs: + i18n-coverage: + name: Check translation coverage + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Lightweight informational check: reports translation coverage per + # language and fails the job only if any language drops below + # COVERAGE_WARN_PCT (default 80%). It never blocks a release for + # in-flight translations -- raise the threshold once the + # auto-translate workflow has caught up. + - name: Run i18n coverage check + run: python3 scripts/test_i18n_coverage.py + + - name: Upload coverage report + if: always() + uses: actions/upload-artifact@v4 + with: + name: i18n-coverage-report + path: /tmp/i18n_coverage_report.txt + if-no-files-found: ignore diff --git a/.github/workflows/pr-summary.yml b/.github/workflows/pr-summary.yml index 5a019787..6863cf0a 100644 --- a/.github/workflows/pr-summary.yml +++ b/.github/workflows/pr-summary.yml @@ -33,7 +33,7 @@ jobs: -- '*.swift' '*.m' '*.h' '*.c' '*.xib' '*.storyboard' \ '*.xcconfig' '*.plist' '*.yaml' '*.yml' '*.json' \ 'Package.swift' 'Package.resolved' 'Podfile' 'Podfile.lock' \ - '.circleci/**' '.github/**' \ + 'ci_scripts/**' '.github/**' \ | head -c 32000 > pr_diff.txt # Summarise stats for the prompt @@ -83,20 +83,20 @@ jobs: /\.xib$|\.storyboard$|Color|Theme|Style|Asset/.test(f) ); const isConfig = files.some(f => - /\.xcconfig$|\.plist$|Package\.swift$|Podfile|\.circleci|\.github/.test(f) + /\.xcconfig$|\.plist$|Package\.swift$|Podfile|ci_scripts|\.github/.test(f) ); const isPerfOrFix = files.some(f => /Fix|Bug|Crash|Patch|Hotfix/i.test(f) ); const isCICD = files.some(f => - /\.circleci|\.github|fastlane|Fastfile|Matchfile/.test(f) + /ci_scripts|\.github|fastlane|Fastfile|Matchfile/.test(f) ); // --- Build Claude prompt --- const prompt = ` You are a senior iOS engineer reviewing a pull request for the Brainwallet iOS app (Swift, SwiftUI + UIKit hybrid, AppDelegate architecture, Firebase Analytics/Crashlytics, - CircleCI + Xcode Cloud CI, Swift Package Manager dependencies). + Xcode Cloud CI, Swift Package Manager dependencies). Repository layout: - brainwallet/ → Main app source (Swift, SwiftUI Views, ViewControllers) @@ -105,7 +105,7 @@ jobs: - BrainwalletUITests/ → XCUITest UI test suite - Modules/ → SPM local modules - Private/ → Private keys / provisioning (gitignored in prod) - - .circleci/ → CircleCI pipeline config + - ci_scripts/ → Xcode Cloud custom build scripts - .github/ → GitHub Actions workflows - fastlane/ → Fastlane lanes (scan, match, deliver) - BrainwalletHybrid.xcworkspace → Workspace root diff --git a/README.md b/README.md index 39442d7f..37ca34a8 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,7 @@ **Brainwallet** is a free, open-source, self-custodial [Litecoin](https://litecoin.org) wallet for iOS. Your seed phrase and keys stay on your device — Brainwallet never has custody of your funds. -### CircleCI status [![Release](https://img.shields.io/github/v/release/gruntsoftware/ios?style=plastic)](https://github.com/gruntsoftware/ios/releases) -[![CircleCI](https://dl.circleci.com/status-badge/img/gh/gruntsoftware/ios/tree/main.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/gruntsoftware/ios/tree/main) -[![Tests](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/grunt-claude-bot/1121e3f9e22e6fda5273ea795bcc85be/raw/tests-badge.json)](https://dl.circleci.com/status-badge/redirect/gh/gruntsoftware/ios/tree/develop) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) ## App Store @@ -56,7 +53,7 @@ ## Testing -Test targets are `BrainwalletUnitTests` and `BrainwalletUITests`. Run via Fastlane, e.g. `bundle exec fastlane run_unit_tests_iPhone16ProMax` (see `fastlane/Fastfile` for other lanes). CI runs on CircleCI (`.circleci/config.yml`). +Test targets are `BrainwalletUnitTests` and `BrainwalletUITests`. Run via Fastlane, e.g. `bundle exec fastlane run_unit_tests_iPhone16ProMax` (see `fastlane/Fastfile` for other lanes). CI runs on Xcode Cloud (`ci_scripts/`); i18n translation coverage is checked separately via a GitHub Actions workflow (`.github/workflows/i18n-coverage.yml`). ## Security diff --git a/ci_scripts/ci_post_clone.sh b/ci_scripts/ci_post_clone.sh index 354729e5..99d3766a 100755 --- a/ci_scripts/ci_post_clone.sh +++ b/ci_scripts/ci_post_clone.sh @@ -10,8 +10,7 @@ RESOURCES_DIR="$CI_PRIMARY_REPOSITORY_PATH/brainwallet/PreLaunchResources" # Write the files to the correct location. # These env vars are base64-encoded in Xcode Cloud's environment variable -# settings (App Store Connect), same as their CircleCI counterparts in -# .circleci/config.yml's "Setup environment files" step -- keep both in sync. +# settings (App Store Connect). echo "$GOOGLE_SERVICES_PLIST" | base64 --decode > "$RESOURCES_DIR/GoogleService-Info.plist" echo "$REMOTE_CONFIG_DEFAULTS" | base64 --decode > "$RESOURCES_DIR/remote-config-defaults.plist" echo "$DEBUG_SERVICE_DATA" | base64 --decode > "$RESOURCES_DIR/service-data.plist" diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 477004b5..7f19e273 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -5,7 +5,6 @@ default_platform :ios platform :ios do before_all do - setup_circle_ci update_fastlane end diff --git a/scripts/test_i18n_coverage.py b/scripts/test_i18n_coverage.py index 3698bd31..d021545d 100755 --- a/scripts/test_i18n_coverage.py +++ b/scripts/test_i18n_coverage.py @@ -203,7 +203,7 @@ def main(): print(f"\n{'─' * 62}\n") - # Write a plain-text copy for the CircleCI artifact store + # Write a plain-text copy for the CI artifact store (GitHub Actions) artifact_path = Path("/tmp/i18n_coverage_report.txt") try: with open(artifact_path, "w", encoding="utf-8") as f: From 5220506f1562acf3f099e36929a8a56778e6651a Mon Sep 17 00:00:00 2001 From: kcw-grunt Date: Mon, 17 Aug 2026 12:21:34 +0100 Subject: [PATCH 2/3] remove duplicate bwiosgdx --- brainwallet.xcodeproj/project.pbxproj | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/brainwallet.xcodeproj/project.pbxproj b/brainwallet.xcodeproj/project.pbxproj index fe4f5f31..de141069 100644 --- a/brainwallet.xcodeproj/project.pbxproj +++ b/brainwallet.xcodeproj/project.pbxproj @@ -379,8 +379,6 @@ C38F57252FD490430005538A /* boingspringmouthharp042013.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = C38F57242FD490430005538A /* boingspringmouthharp042013.mp3 */; }; C395E2B83013770A00CF5F5B /* EnterPinForSeedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C395E2B2301376FE00CF5F5B /* EnterPinForSeedView.swift */; }; C3B0BBC32FF6DB11000D5DB8 /* AlertToast in Frameworks */ = {isa = PBXBuildFile; productRef = C3B0BBC22FF6DB11000D5DB8 /* AlertToast */; }; - C3BC69AF30323E3500A04919 /* BWIOSGdx.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = C335491D2FE14FA1007741F5 /* BWIOSGdx.xcframework */; }; - C3BC69B030323E3500A04919 /* BWIOSGdx.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C335491D2FE14FA1007741F5 /* BWIOSGdx.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; C3C95F082FA13DEF002E2BEE /* SignupAskView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C95F072FA13DEF002E2BEE /* SignupAskView.swift */; }; C3CE34612FFD8D1F004A2ECA /* LilitaOne.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C3CE34602FFD8D1F004A2ECA /* LilitaOne.ttf */; }; C3E18A9F2F9E657000966C74 /* ShopCardsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E18A9E2F9E656600966C74 /* ShopCardsView.swift */; }; @@ -467,7 +465,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - C3BC69B030323E3500A04919 /* BWIOSGdx.xcframework in Embed Frameworks */, 24FF3E6A2E898D4000FF43C5 /* BrainwalletiOSPrivateGeneralPurpose.framework in Embed Frameworks */, ); name = "Embed Frameworks"; @@ -1027,7 +1024,6 @@ C3B0BBC32FF6DB11000D5DB8 /* AlertToast in Frameworks */, C32553C92FE090D1008B1861 /* AVFoundation.framework in Frameworks */, 246F97EA2E846230001923A6 /* BrainwalletiOSPrivateGeneralPurpose.framework in Frameworks */, - C3BC69AF30323E3500A04919 /* BWIOSGdx.xcframework in Frameworks */, C32553C52FE090A6008B1861 /* OpenAL.framework in Frameworks */, 24D91D0B2166923E0077A619 /* UserNotifications.framework in Frameworks */, 2460284F2E5F178400CDEB7A /* FirebaseAnalytics in Frameworks */, @@ -3216,7 +3212,7 @@ CODE_SIGN_ENTITLEMENTS = brainwallet/brainwallet.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2602307; + CURRENT_PROJECT_VERSION = 2602308; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEPLOYMENT_POSTPROCESSING = NO; DEVELOPMENT_TEAM = U8WDVUDC2P; @@ -3418,7 +3414,7 @@ CODE_SIGN_ENTITLEMENTS = brainwallet/brainwallet.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2602307; + CURRENT_PROJECT_VERSION = 2602308; DEPLOYMENT_POSTPROCESSING = YES; DEVELOPMENT_TEAM = U8WDVUDC2P; DOCC_EXTRACT_SWIFT_INFO_FOR_OBJC_SYMBOLS = YES; From 15faa2ba80e7e7acd38a712db9a49690d617a138 Mon Sep 17 00:00:00 2001 From: kcw-grunt Date: Mon, 17 Aug 2026 12:26:44 +0100 Subject: [PATCH 3/3] bumped code --- brainwallet.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/brainwallet.xcodeproj/project.pbxproj b/brainwallet.xcodeproj/project.pbxproj index de141069..29b7a260 100644 --- a/brainwallet.xcodeproj/project.pbxproj +++ b/brainwallet.xcodeproj/project.pbxproj @@ -3212,7 +3212,7 @@ CODE_SIGN_ENTITLEMENTS = brainwallet/brainwallet.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2602308; + CURRENT_PROJECT_VERSION = 2602309; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEPLOYMENT_POSTPROCESSING = NO; DEVELOPMENT_TEAM = U8WDVUDC2P; @@ -3414,7 +3414,7 @@ CODE_SIGN_ENTITLEMENTS = brainwallet/brainwallet.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2602308; + CURRENT_PROJECT_VERSION = 2602309; DEPLOYMENT_POSTPROCESSING = YES; DEVELOPMENT_TEAM = U8WDVUDC2P; DOCC_EXTRACT_SWIFT_INFO_FOR_OBJC_SYMBOLS = YES;