Flutter release flow
npx -y skills add mike623/agent-skills --skill flutter-release-flowAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Reusable standard release setup for Mike's Flutter apps. Use before creating or modifying Flutter Fastlane, mise, CocoaPods, Apple signing, TestFlight, App Store, or Play Store release automation.
SKILL.md
13.4 KB, as published. Nobody here has run it
Flutter Release Flow
Use this skill for any current or future Flutter app that needs a repeatable release setup.
Known apps using this standard:
- Catty2/PurrSafe:
<path-to-purrsafe-or-existing-app> - Mr. Carson mobile:
<path-to-mr-carson-or-existing-app> - CoupleCup:
<path-to-couplecup-or-existing-app>
For a new Flutter app, copy this standard unless there is a specific reason not to.
Standard toolchain
Use project-local, reproducible tools. Do not rely on whichever Ruby/Fastlane/CocoaPods happens to be installed globally.
Required files for a new Flutter releaseable app
At the Flutter app root:
.mise.toml
Gemfile
Gemfile.lock
pubspec.yaml
ios/fastlane/Fastfile
ios/fastlane/Appfile
ios/fastlane/Matchfile
ios/fastlane/.env.example
ios/fastlane/.env # local only, gitignored
If the project already has root-level fastlane/ and it works, do not move it casually. For new projects, prefer ios/fastlane/.
mise
Prefer mise for Ruby and other runtime versions.
Recommended .mise.toml:
[tools]
ruby = "3.3.11"
Use:
mise install
mise exec -- bundle install
mise exec -- bundle exec fastlane lanes
Do not manually patch PATH for Ruby when mise exec is available.
Bundler / Fastlane / CocoaPods
Recommended app-root Gemfile:
source "https://rubygems.org"
gem "fastlane", "~> 2.225"
gem "cocoapods", "~> 1.16"
Use Bundler commands:
mise exec -- bundle install
mise exec -- bundle exec pod --version
mise exec -- bundle exec fastlane lanes
For iOS pods:
cd ios
mise exec -- bundle exec pod install
If Gemfile is at app root and you run from ios/, use one of:
BUNDLE_GEMFILE=../Gemfile mise exec -- bundle exec fastlane lanes
BUNDLE_GEMFILE=../Gemfile mise exec -- bundle exec pod install
or keep a simple ios/Gemfile only if the project already follows that convention.
Standard release model
All apps should follow the same release spine even when app-specific lanes exist.
- Load App Store Connect API key from env.
- Sync signing with
matchfrom the shared Apple-team branch. - Keep normal lanes read-only for signing assets.
- Ensure App Store compliance metadata is present before building.
- Bump build number from TestFlight or CI run number.
- Build Flutter release artifacts with Ruby/Bundler env stripped.
- Archive/export with Fastlane/Xcode signing.
- Upload to TestFlight for
beta. - Submit/upload to App Store for
releaseonly when explicitly requested.
Release notes / What to Test metadata
Before any TestFlight/App Store release, update the maintained release-note file from recent commits/changelogs. Do this for every active Fastlane project, not just the repo currently in focus.
Discovery command:
find <workspace-root> -path '*/fastlane/Fastfile' \
-not -path '*/.claude/worktrees/*' \
-not -path '*/node_modules/*' \
-print | sort
Current active projects and metadata conventions:
- Project-specific TestFlight/App Store metadata paths should use repo-relative paths, for example
ios/fastlane/metadata/en-US/release_notes.txtorfastlane/metadata/whats_new.txt. - Do not commit private absolute workspace paths in this public skill repo; keep local project mappings in private notes/config.
Metadata workflow:
- Inspect branch/status and recent commits:
git status --short --branchandgit log --oneline --decorate -30. - Prefer changes since the last release/build bump when obvious; otherwise summarize the most recent user-facing feature/fix commits.
- Write tester-facing bullets: features first, then fixes/reliability, then a short
Please test:checklist for TestFlight. - Ensure the relevant beta lane passes the file content into
upload_to_testflight(changelog: File.read(...).strip)instead of hardcoded text like"Latest build". - Keep release notes free of internal-only details, secrets, private URLs, and excessive implementation jargon unless testers need it.
- Verify with
ruby -c <fastlane/Fastfile>and inspectgit diffbefore reporting.
Standard env names
Use these names across all apps. Legacy names can exist as fallbacks only.
APP_IDENTIFIER=<bundle id>
APP_NAME=<display/app-store name>
APP_SKU=<sku>
APPLE_TEAM_ID=<apple team id>
TEAM_ID=<apple team id>
ITC_TEAM_ID=
APPLE_ID=
ASC_KEY_ID=
ASC_ISSUER_ID=
ASC_KEY_FILEPATH=
ASC_KEY_CONTENT=
MATCH_GIT_URL=<match git url>
MATCH_GIT_BRANCH=apple-team-<team id>
MATCH_TYPE=appstore
MATCH_READONLY=true
MATCH_PASSWORD=***
Never commit real .env, .p8, certificates, profiles, provisioning profiles, or secret output.
Signing policy
- Distribution certificate is Apple-team/account-level.
- Provisioning profiles are app/bundle-ID-level.
- Shared match branch:
apple-team-<team id>. - Normal
beta,build, andreleaselanes usereadonly: true. - Local lanes must not create, switch, unlock, delete, reset, or pass explicit keychain settings. Never call
setup_ci,create_keychain,unlock_keychain,delete_keychain, ormatch(..., keychain_name:/keychain_password:)for local dev. Temporary keychain setup is only allowed in explicitly CI-only paths guarded byENV["CI"] == "true". - One-time
certificates/create_profilelanes may usereadonly: falseonly to create missing app profiles. - Do not revoke, nuke, rotate, or regenerate team distribution certificates without explicit approval.
App Store compliance metadata
For iOS apps that only use standard HTTPS/TLS encryption and do not use non-exempt/custom cryptography, set this in ios/Runner/Info.plist before building:
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
This prevents future TestFlight/App Store builds from showing Missing Compliance for export compliance. It only affects builds created after the key is present; already-uploaded builds may still need compliance answered manually in App Store Connect or a new build uploaded.
When adding this for a project, verify with:
/usr/libexec/PlistBuddy -c 'Print :ITSAppUsesNonExemptEncryption' ios/Runner/Info.plist
Only set it to false when the app does not use non-exempt encryption. If the app has custom crypto, VPN, secure messaging, proprietary encryption, or regulated crypto features, stop and get the correct compliance answer instead of assuming exemption.
Standard Fastlane files
Canonical templates are provided in this skill directory:
templates/.mise.tomltemplates/Gemfiletemplates/Appfiletemplates/Matchfiletemplates/Fastfiletemplates/.env.example
For new projects, copy these templates first and replace placeholders (<bundle id>, <app name>, <sku>). Then adapt only app-specific dart-defines, metadata, IAP, Sentry, or dependency workarounds.
Appfile
app_identifier(ENV["APP_IDENTIFIER"] || "<bundle id>")
team_id(ENV["APPLE_TEAM_ID"] || ENV["TEAM_ID"] || "<apple team id>")
apple_id(ENV["APPLE_ID"])
itc_team_id(ENV["ITC_TEAM_ID"])
Matchfile
git_url(ENV["MATCH_GIT_URL"] || "<match git url>")
git_branch(ENV["MATCH_GIT_BRANCH"] || "apple-team-<team id>")
storage_mode("git")
type(ENV["MATCH_TYPE"] || "appstore")
app_identifier(ENV["APP_IDENTIFIER"] || "<bundle id>")
team_id(ENV["APPLE_TEAM_ID"] || ENV["TEAM_ID"] || "<apple team id>")
readonly(ENV["MATCH_READONLY"].nil? ? true : ENV["MATCH_READONLY"] == "true")
Fastfile standard helpers
Prefer this helper shape in each app, adapted only for app-specific defaults:
def app_identifier
ENV["APP_IDENTIFIER"] || "<bundle id>"
end
def apple_team_id
ENV["APPLE_TEAM_ID"] || ENV["TEAM_ID"] || "<apple team id>"
end
def asc_api_key
opts = {
key_id: ENV.fetch("ASC_KEY_ID"),
issuer_id: ENV.fetch("ASC_ISSUER_ID"),
in_house: false
}
if ENV["ASC_KEY_FILEPATH"].to_s.strip != ""
opts[:key_filepath] = ENV["ASC_KEY_FILEPATH"]
else
opts[:key_content] = Base64.decode64(ENV.fetch("ASC_KEY_CONTENT"))
end
app_store_connect_api_key(**opts)
end
def clean_flutter_subprocess_env!
%w[GEM_HOME GEM_PATH BUNDLE_GEMFILE BUNDLE_BIN_PATH RUBYOPT RUBYLIB].each { |k| ENV.delete(k) }
ENV["PATH"] = "/opt/homebrew/bin:#{ENV['PATH']}" unless ENV["PATH"].to_s.include?("/opt/homebrew/bin")
end
def match_profile_name
mapping = Actions.lane_context[SharedValues::MATCH_PROVISIONING_PROFILE_MAPPING] || {}
mapping[app_identifier] || "match AppStore #{app_identifier}"
end
def ensure_ios_export_compliance_metadata!
plist = "Runner/Info.plist"
value = `/usr/libexec/PlistBuddy -c 'Print :ITSAppUsesNonExemptEncryption' #{plist} 2>/dev/null`.strip
return if value == "false"
sh("/usr/libexec/PlistBuddy -c 'Add :ITSAppUsesNonExemptEncryption bool false' #{plist} 2>/dev/null || /usr/libexec/PlistBuddy -c 'Set :ITSAppUsesNonExemptEncryption false' #{plist}")
UI.message("Set ITSAppUsesNonExemptEncryption=false for standard HTTPS/TLS export compliance")
end
def install_signing_assets(api_key, readonly: true)
# Local dev must not touch keychains: do not create, switch, unlock, delete,
# reset, or pass explicit keychain settings. Temporary keychain setup belongs
# only in CI=true-only lanes, never in local beta/build/release lanes.
# App Store Connect API-key auth avoids Apple ID/2FA, but macOS codesigning
# still requires the distribution private key to be available through the
# existing macOS signing environment.
match(type: "appstore", readonly: readonly, api_key: api_key, app_identifier: app_identifier)
end
If an existing app uses load_asc_api_key, either keep it as an alias or make it equivalent.
Standard iOS beta lane spine
The lane body can differ for app-specific needs, but the order should remain. Use install_signing_assets(api_key) for local project lanes. Local dev must never create, switch, unlock, delete, reset, or pass explicit keychain settings; temporary keychains are only acceptable in explicitly CI-only paths guarded by ENV["CI"] == "true".
lane :beta do
api_key = asc_api_key
install_signing_assets(api_key)
update_code_signing_settings(
use_automatic_signing: false,
path: "Runner.xcodeproj",
team_id: apple_team_id,
code_sign_identity: "Apple Distribution",
profile_name: "match AppStore #{app_identifier}",
targets: ["Runner"]
)
begin
latest = latest_testflight_build_number(api_key: api_key, app_identifier: app_identifier)
increment_build_number(xcodeproj: "Runner.xcodeproj", build_number: latest + 1)
rescue => e
UI.important("Could not read TestFlight build number (#{e.message}); falling back to local increment.")
increment_build_number(xcodeproj: "Runner.xcodeproj")
end
ensure_ios_export_compliance_metadata!
sh("cd ../.. && env -u GEM_HOME -u GEM_PATH -u BUNDLE_GEMFILE -u BUNDLE_BIN_PATH -u RUBYOPT flutter build ios --release --no-codesign")
clean_flutter_subprocess_env!
ipa_path = build_app(
workspace: "Runner.xcworkspace",
scheme: "Runner",
export_method: "app-store",
export_options: {
provisioningProfiles: {
app_identifier => "match AppStore #{app_identifier}"
}
}
)
upload_to_testflight(
api_key: api_key,
ipa: ipa_path,
skip_waiting_for_build_processing: true
)
end
Use skip_waiting_for_build_processing: false only when a later step truly needs processed build state or external tester distribution.
Standard Android lane spine
For Android, prefer AAB and Play internal track:
platform :android do
desc "Build and upload to Play Store internal track"
lane :beta do
sh("cd .. && flutter build appbundle --release")
upload_to_play_store(
track: "internal",
aab: "../build/app/outputs/bundle/release/app-release.aab",
skip_upload_screenshots: true,
skip_upload_images: true
)
end
end
Keep Android signing secrets in Gradle/CI secret stores, not in tracked Fastlane files.
App-specific exceptions to preserve
- PurrSafe has App Store metadata/IAP/Sentry/managed-profile logic. Do not delete those lanes.
- CoupleCup has an
objective_c.frameworkmin-iOS patch in its archive/export flow. Preserve it until the dependency issue is fixed. - Mr. Carson currently has the cleanest reference implementation for the base iOS TestFlight flow.
New-project setup checklist
- Add
.mise.tomlwith Ruby. - Add
Gemfilewith Fastlane and CocoaPods. - Run
mise install && mise exec -- bundle install. - Add
ios/fastlane/Appfile,Matchfile,Fastfile,.env.example. - Add
.gitignorerules for:ios/fastlane/.env*.p8*.mobileprovision*.cer*.p12
- Verify
git check-ignore -v ios/fastlane/.env. - Run
mise exec -- bundle exec fastlane lanesfrom the chosen Fastlane working directory. - Do not run Apple-side write lanes until the app record/bundle ID/profile creation is intentionally requested.
Verification checklist
Before saying release automation is ready:
ruby -c <app fastlane Fastfile>
git -C <repo> check-ignore -v <path-to-fastlane/.env> || true
git -C <repo> diff -- <fastlane files>
cd <fastlane working dir> && mise exec -- bundle exec fastlane lanes
If the repo does not use mise yet, add it rather than falling back to system Ruby for a new project.
Do not run beta, release, create_profile, or certificates unless the user explicitly asks for Apple-side side effects.