Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
8 changes: 7 additions & 1 deletion app/fastlane/DEV.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ yarn mobile-local-deploy:android # Deploy Android to Google Play Internal Testi

**Why internal testing?** This provides the same safety as GitHub runner deployments while allowing you to use your local machine for building.

After running a local iOS deploy, reset the Xcode project to avoid committing build artifacts:

```bash
./scripts/cleanup-ios-build.sh
```

### Direct Fastlane Commands (Not Recommended)

⚠️ **Use the confirmation script above instead of these direct commands.**
Expand Down Expand Up @@ -218,7 +224,7 @@ Fastlane requires various secrets to interact with the app stores and sign appli
| `IOS_P12_PASSWORD` | Password for the p12 certificate file |
| `IOS_TEAM_ID` | Apple Developer Team ID |
| `IOS_TEAM_NAME` | Apple Developer Team name |
| `IOS_TESTFLIGHT_GROUPS` | Comma-separated list of TestFlight groups to distribute the app to |
| `IOS_TESTFLIGHT_GROUPS` | Comma-separated list of **external** TestFlight groups to distribute the app to |

#### Slack Integration Secrets 📱

Expand Down
11 changes: 8 additions & 3 deletions app/fastlane/Fastfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ android_has_permissions = false

# Project configuration
PROJECT_NAME = ENV["IOS_PROJECT_NAME"]
APP_NAME = ENV["IOS_PROJECT_NAME"] || begin
JSON.parse(File.read("../app.json"))["displayName"]
rescue
nil
end || "MobileApp"
PROJECT_SCHEME = ENV["IOS_PROJECT_SCHEME"]
SIGNING_CERTIFICATE = ENV["IOS_SIGNING_CERTIFICATE"]

Expand Down Expand Up @@ -54,7 +59,7 @@ platform :ios do
upload_to_testflight(
api_key: result[:api_key],
distribute_external: true,
# TODO: fix error about the groups not being set correctly, fwiw groups are set in the app store connect
# Only external TestFlight groups are valid here
groups: ENV["IOS_TESTFLIGHT_GROUPS"].split(","),
changelog: "",
skip_waiting_for_build_processing: false,
Expand All @@ -66,7 +71,7 @@ platform :ios do
file_path: result[:ipa_path],
channel_id: ENV["SLACK_CHANNEL_ID"],
initial_comment: "🍎 iOS v#{package_version} (Build #{result[:build_number]}) deployed to TestFlight",
title: "#{PROJECT_NAME}-#{package_version}-#{result[:build_number]}.ipa",
title: "#{APP_NAME}-#{package_version}-#{result[:build_number]}.ipa",
)
else
UI.important("Skipping Slack notification: SLACK_CHANNEL_ID not set.")
Expand Down Expand Up @@ -266,7 +271,7 @@ platform :android do
file_path: android_aab_path,
channel_id: ENV["SLACK_CHANNEL_ID"],
initial_comment: "🤖 Android v#{package_version} (Build #{version_code}) deployed to #{target_platform}",
title: "#{PROJECT_NAME}-#{package_version}-#{version_code}.aab",
title: "#{APP_NAME}-#{package_version}-#{version_code}.aab",
)
else
UI.important("Skipping Slack notification: SLACK_CHANNEL_ID not set.")
Expand Down
1 change: 1 addition & 0 deletions app/fastlane/helpers/slack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module Slack
def upload_file_to_slack(file_path:, channel_id:, initial_comment: nil, thread_ts: nil, title: nil)
slack_token = ENV["SLACK_API_TOKEN"]
report_error("Missing SLACK_API_TOKEN environment variable.", nil, "Slack Upload Failed") if slack_token.to_s.strip.empty?
report_error("Missing SLACK_CHANNEL_ID environment variable.", nil, "Slack Upload Failed") if channel_id.to_s.strip.empty?
report_error("File not found at path: #{file_path}", nil, "Slack Upload Failed") unless File.exist?(file_path)

file_name = File.basename(file_path)
Expand Down
14 changes: 14 additions & 0 deletions app/fastlane/test/helpers_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,20 @@ def test_verify_env_vars_some_missing
assert_equal ["MISSING_VAR1", "EMPTY_VAR", "WHITESPACE_VAR"], missing
end

def test_upload_file_to_slack_missing_channel
ENV["SLACK_API_TOKEN"] = "token"
file = Tempfile.new(["artifact", ".txt"])
file.write("data")
file.close

assert_raises(FastlaneCore::Interface::FastlaneCommonException) do
Fastlane::Helpers.upload_file_to_slack(file_path: file.path, channel_id: "")
end
ensure
file.unlink
ENV.delete("SLACK_API_TOKEN")
end

private

def clear_test_env_vars
Expand Down
24 changes: 24 additions & 0 deletions app/scripts/cleanup-ios-build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash
# Reset Xcode project after local fastlane builds
set -euo pipefail

PROJECT_NAME="${IOS_PROJECT_NAME:-Self}"
PBXPROJ="ios/${PROJECT_NAME}.xcodeproj/project.pbxproj"

if [ ! -f "$PBXPROJ" ]; then
echo "Project file not found: $PBXPROJ" >&2
exit 1
fi

MARKETING_VERSION=$(grep -m1 "MARKETING_VERSION =" "$PBXPROJ" | awk '{print $3}' | tr -d ';')
CURRENT_VERSION=$(grep -m1 "CURRENT_PROJECT_VERSION =" "$PBXPROJ" | awk '{print $3}' | tr -d ';')

git checkout -- "$PBXPROJ"

if sed --version >/dev/null 2>&1; then
sed -i -e "s/\(MARKETING_VERSION = \).*/\1$MARKETING_VERSION;/" -e "s/\(CURRENT_PROJECT_VERSION = \).*/\1$CURRENT_VERSION;/" "$PBXPROJ"
else
sed -i '' -e "s/\(MARKETING_VERSION = \).*/\1$MARKETING_VERSION;/" -e "s/\(CURRENT_PROJECT_VERSION = \).*/\1$CURRENT_VERSION;/" "$PBXPROJ"
fi

echo "Reset $PBXPROJ"
43 changes: 43 additions & 0 deletions app/scripts/tests/cleanup-ios-build.test.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const fs = require('fs');
const path = require('path');
const os = require('os');
const { execSync } = require('child_process');
const { describe, it } = require('node:test');
const assert = require('node:assert');

const SCRIPT = path.join(__dirname, '../cleanup-ios-build.sh');

describe('cleanup-ios-build.sh', () => {
it('resets pbxproj and reapplies versions', () => {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'cleanup-test-'));
const projectName = 'MyApp';
const iosDir = path.join(tmp, 'ios', `${projectName}.xcodeproj`);
fs.mkdirSync(iosDir, { recursive: true });
const pbxPath = path.join(iosDir, 'project.pbxproj');
fs.writeFileSync(
pbxPath,
'CURRENT_PROJECT_VERSION = 1;\nMARKETING_VERSION = 1.0.0;\n',
);

const cwd = process.cwd();
process.chdir(tmp);
execSync('git init -q');
execSync('git config user.email "[email protected]"');
execSync('git config user.name "Test"');
execSync(`git add ${pbxPath}`);
execSync('git commit -m init -q');

fs.writeFileSync(
pbxPath,
'CURRENT_PROJECT_VERSION = 2;\nMARKETING_VERSION = 2.0.0;\nSomeArtifact = 123;\n',
);

execSync(`IOS_PROJECT_NAME=${projectName} bash ${SCRIPT}`);
process.chdir(cwd);

const result = fs.readFileSync(pbxPath, 'utf8');
assert(result.includes('CURRENT_PROJECT_VERSION = 2;'));
assert(result.includes('MARKETING_VERSION = 2.0.0;'));
assert(!result.includes('SomeArtifact'));
});
});
Loading