easy_setup 0.3.0
easy_setup: ^0.3.0 copied to clipboard
Take a Flutter app from code to both stores from one YAML — service provisioning, native config, store assets, signing, builds, and uploads.
0.3.0 #
create scaffolds your folder structure, not just the app.
- Scaffolded IDs end in the app directory's segment —
create safezone --workspaceyieldscom.example.safezone.customeron both platforms (underscores stripped, no camel-casing; the pilot convention). flutter create is driven with the segment as its project name so gradle, the MainActivity package path, and the Xcode project are all flutter-authentic; the Dart package is renamed back afterwards create --workspacescaffolds a melos monorepo around the new app:flutter_app/with the workspace pubspec, shared lints, amason make pagebrick, an FVM pin of the installed Flutter version,script/bootstrap.sh, an architecture doc, the app atapps/customer, and an emptypackages/. The repository directory and the Dart package are decoupled —create my-product --workspaceputs packagemy_productatapps/customer(--project-nameoverrides)create --template <dir>scaffolds a user-authored structure instead: files are copied with{{es:name}}/{{es:app_name}}/{{es:org}}/{{es:bundle_id}}/{{es:package_name}}/{{es:flutter_version}}substituted in paths and text (binary files pass through; mason's own{{...}}placeholders survive — that is what thees:namespace is for). An empty.easy_setup_appmarker file names the one directory whereflutter createruns- Scaffolding runs
git initat the scaffold root before init, so the CI caller workflow lands at the repository root withproject-rootwired to the app path — already inside a repository, it skips and uses that root
0.2.0 #
The iOS project becomes a generated artifact: ios_project now derives an
XcodeGen spec from the yaml and generates ios/Runner.xcodeproj from it,
instead of editing the committed project in place.
- Breaking:
ios/Runner.xcodeprojis generated, not edited. Theios_projectstep writesios/project.yml(derived, easy_setup-owned) and runsxcodegen generate(brew install xcodegen); the.xcodeproj/.xcworkspaceare gitignored build artifacts. On the first run against a committed project the step prints the switchover notice, including the one-timegit rm -r --cached. ThexcodeprojRuby gem path is gone, and doctor now checks forxcodegeninstead — a missing binary still only skips generation, never fails the run ios/project_overlay.ymlcarries your project customizations — seeded once, merged into the spec via XcodeGeninclude:. Additive by design: on a conflict the generated file wins, so generator-owned settings are changed in easy_setup.yaml. Editing the overlay re-triggers generation (its content hash is stamped into the spec)ios.team_idnow lands in the project asDEVELOPMENT_TEAMwith automatic signing, so deviceflutter runsurvives regeneration; a regeneration reverts whatcerts --applywrote (reruncerts), and a regenerated project with CocoaPods asks for onepod install- A removed
ios.localizationslocale is pruned — file, empty.lprojfolder, and known region. Generation leaves no dangling pbxproj reference, which is what used to keep removal manual. Registration ofPrivacyInfo.xcprivacy,GoogleService-Info.plist, and.lprojvariant groups now comes free from the spec's directory-glob sources - Step order:
ios_projectmoved beforefirebase—flutterfire configureneeds the generated project, which does not exist on a fresh clone until the step runs; the firebase step now says exactly that when the project is missing, and only configures the platforms the project actually has (an Android-only project is not asked to configure iOS) - A
project.ymleasy_setup did not write (v1-flavored, hand-authored) is reported and left alone — nothing is generated over it
0.1.0 #
First public release of the v2 toolkit: one easy_setup.yaml takes a
Flutter app from "code exists" to "live in both stores" — without opening
Xcode, and past each vendor's one-time setup, without opening a web console
again.
- Breaking: the v2 commands (
init,doctor,setup,capture,certs,deploy) use the v2 top-level schema (app:/ios:/android:/build:/branding:/screenshots:/sentry:/amplitude:/firebase:/admob:/site:). A v1easy_setup:root key is detected and rejected with guidance. The v1flavorandci-cdcommands still run on the old schema — seedoc/v1.md - Setup Kit — Sentry (project creation + DSN + symbol-upload wiring),
Amplitude (key verification + env injection), Firebase provisioning,
AdMob (API lookup/creation, ID injection, generated
lib/ads/ad_ids.dart,--adoptto read existing ad units into the yaml), iOS capabilities (entitlements with a per-configuration aps-environment split, Developer Portal via the ASC API), declared Info.plist keys, iOS project settings (bundle id, deployment target,PrivacyInfo.xcprivacy, localizedInfoPlist.strings— through thexcodeprojgem), app icons (SVG → all iOS/Android sizes), store screenshots (HTML template → store-spec PNGs), promo site, and store listings fromeasy_setup_store_info.yaml - Deploy Kit —
certsfor local-device signing,deployfor match → build → TestFlight/Play, always authenticated with an App Store Connect API key (never an Apple ID session) - CI — reusable workflows (
release-ios.yml/release-android.yml), referenced by the generated caller at thev1tag; av*tag push deploys to both stores doctorverifies every tool, key, and secret the config asks for, with issuance guidance
0.1.0-dev.11 #
- Sentry setup reads
SENTRY_API_TOKEN(the oldSENTRY_ORG_TOKENname still works). The guidance it replaces was wrong: an organization token has fixed CI scopes and cannot create a project, which is whatsetupdoes. Issue an internal-integration token (Sentry's own recommendation for programmatic project creation) or a personal token withproject:write+org:read. An organization token stays the right kind forSENTRY_AUTH_TOKEN, the build-time symbol upload — doctor now warns when ansntrys_token shows up as the setup token instead of accepting it - New
certscommand — the iOS signing assets a local build needs, whichdeploynever covered:easy_setup certssyncs the development profile through the same match repository and points Debug/Profile/Release at it, soflutter run --releaseinstalls on a device.--type adhoc|appstore,--readonly,--apply/--no-apply,--register-device <UDID>for the registration a development profile requires, and--list-devicesto see what the portal has (AscApiClient.devices(), marking the UDID being registered). Both commands now build their fastlane arguments fromdeploy/ios_signing.dart, which also owns the profile names match generates (match AdHoc, notmatch Adhoc) - deploy's signing switch is narrowed to the Release configuration —
Debug/Profile keep whatever
certswrote, so a deploy no longer breaks the next device run - iOS deploy puts the Xcode project back. The signing switch it needs
(manual + the App Store distribution profile) was left behind, so afterwards
flutter runon a device failed at install — an App Store profile cannot be used for one — and the pbxproj carried an unexplained diff. The project file is now snapshotted and restored in afinally; projects that already commit match's settings see no change deploypasses the env file to the build.flutter build ipaandflutter build appbundlenever carried--dart-define-from-file, so release builds compiledSENTRY_DSN,AMPLITUDE_API_KEYand everyADMOB_*value as empty strings — the SDKs no-op on an empty key, so the upload looked fine and the app reported nothing (found on the dream-diary pilot, whose TestFlight builds had monitoring off). Both deployers now passenv.prod.jsonwhen it exists,build.dart_define_fileoverrides the name (and must exist when named), and a new doctor check warns when the file a release build needs is missing or has empty values- A 403 on project creation now names both of its causes — a token without
project:write, or an org that disables member project creation and so wantsorg:write/team:adminon top (hit on the dream-diary pilot)
Sentry, Amplitude and AdMob, set up without opening a web console.
- admob now resolves the IDs it needs through the AdMob API v1beta
instead of asking for them: apps are matched per platform (Android by
package name, otherwise by app name), ad units by display name, and
whatever is missing is created when the account has creation access.
accounts.apps.create/accounts.adUnits.createare limited access and answer 403 for most publishers — that degrades to a console-creation message, with the lookup still saving the copy-paste - AdMob auth is OAuth user credentials (service accounts are not supported),
taken from
ADMOB_ACCESS_TOKEN, anADMOB_REFRESH_TOKEN+ OAuth client, or gcloud's application-default credentials. Newadmob.auto(default true) turns the lookup off,admob.publisher_idpins the account, andadmob.ad_units.<name>.display_namesets what the lookup matches on - New amplitude step (
amplitude:section): the API key arrives through an environment variable, is verified against the ingestion API with an empty event batch (so the probe cannot pollute the project), and is written into env.json (the dev key, or empty so the SDK no-ops) and env.prod.json.region: euswitches host and recordsAMPLITUDE_SERVER_ZONE. Amplitude has no project-creation API, so creating the project is the one console step — doctor spells it out - sentry now finishes the pubspec side as well: the
sentry_flutterdependency, thesentry_dart_plugindev dependency, and thesentry:block (org / project / upload_debug_symbols) that points symbol upload at the project it just provisioned. Keys the developer added to that block survive;sdk: false/upload_symbols: falseopt out - Dependencies are added with
flutter pub add, so pub resolves the version instead of easy_setup pinning one - doctor: new
Amplitude API keyandAdMob API credentialchecks, and missing AdMob app IDs stop being a warning once a credential can look them up. The AdMob check mints a token instead of trusting that an installed gcloud is a logged-in one env.json/env.prod.jsonpruning is per-key rather than per-prefix: ad units the yaml still declares keep the IDs an earlier run resolved even when this lookup fails, a unit deleted from the yaml still loses its keys, andADMOB_*/AMPLITUDE_*keys the developer added stay put- AdMob matching prefers the store link (the Android package name) over the
display name, and refuses a same-named ad unit whose
adFormatdiffers from the declaredtypeinstead of adopting the wrong unit - With
admob.auto: falsethe yaml is the whole truth again, so an ID dropped from it is pruned from the env files instead of lingering - A self-hosted
SENTRY_URLis written into the pubspecsentry:block as well, so symbol upload targets the same instance the DSN came from — and is dropped again when the config moves back to the hosted service - A rejected wrong-format ad unit also drops the ID an earlier run wrote for it, and doctor stops asking for an AdMob credential when every ID is pinned
- Turning
sentry.upload_symbolsoff after a first run setsupload_debug_symbols: falseinstead of leaving the earliertrueuploading (on a project that never had the block, it writes nothing) - An Amplitude probe that fails for any other reason (5xx, rate limit) is reported as unverified instead of counting as approval
- Step order is now
sentry → amplitude → firebase → admob → … pubspec.yaml'srepositorypoints at Etch-Studio/flutter_easy_setup, the canonical location, instead of the pre-transfer URL that only redirects — it is the link pub.dev will publish (M6)
0.1.0-dev.10 #
Store listings without the web UIs.
- New store setup step, activated by an
easy_setup_store_info.yamlnext to easy_setup.yaml: app-level fields (copyright, categories) and per-locale listing texts (name, subtitle, description, keywords, promotional text, release notes, short_description, URLs) in one file - One source generates both fastlane trees (
fastlane/metadata/{locale}for deliver,fastlane/metadata/android/{locale}for supply) with store character limits enforced at parse time and convergent pruning of removed fields setup --only storeuploads immediately: iOS viadeliver --skip_binary_upload(screenshots included when the M5 pipeline produced them), Android via metadata-onlysupplywhen theandroidsection is configured. Missing credentials degrade to generate-only with a warning- New
deploy --submit(iOS): submits the just-uploaded build for App Store review via deliver (metadata untouched — that is the store step's job). Opt-in, never the default review_informationsection (App Review contact / demo account) — generated into deliver's review_information files. Providing it also avoids deliver's first-version "No data" crash (fastlane/fastlane#20538), and the missing/invalid phone number that App Store Connect hard-requires is warned about up front. Verified end-to-end on the dream-diary pilotage_ratingsection (questionnaire answers, snake_case keys) → generated as deliver'sapp_rating_config_pathJSON (camelCase ASC attributes) and wired into the upload; removed section prunes the JSON- Manual by design (no official ASC API — same policy as app record creation): App Privacy data-collection labels (the step names the one-time web location), pricing
- New site step (
site:section): generates the promo/support/ privacy pages every store listing needs, aSITE_BRIEF.mdwith the app's facts, anapp-siteClaude Code skill, and a GitHub Pages workflow. Pages are created but never overwritten, so hand edits and AI redesigns survive; the derived Pages URLs are written intoeasy_setup_store_info.yaml(existing URLs win) so the store step uploads them
0.1.0-dev.9 #
Monorepo-aware init (from the dream-diary pilot):
- The caller workflow is now generated at the git repository root
(the only place GitHub Actions reads workflows from), and when the
Flutter app lives in a subdirectory the
project-rootinput is wired into both jobs automatically — no manual configuration needed - BREAKING:
initoutside a Flutter project no longer falls back to the current directory (which scattered the skeleton at a monorepo root); it now fails with guidance to run inside the app or pass--project-root
0.1.0-dev.8 #
v2 rebuild, milestone M5b (see V2_PLAN.md §5.2): screenshot composition.
- New screenshots setup step — the marketing-composition layer of
the two-layer pipeline. Raw captures under
assets/store/screenshots/raw/{locale}/{device}/*.pngare composed onto store-spec canvases and written where fastlane deliver/supply upload from:- iOS:
fastlane/screenshots/{locale}/— iPhone 6.9" 1320×2868 and iPad 13" 2064×2752 (the two sizes Apple auto-scales the rest from) - Android:
fastlane/metadata/android/{locale}/images/ phoneScreenshots/(1080×1920) +featureGraphic.png(1024×500, validated)
- iOS:
- captions.yaml (
screenshots.captions): background/text colors and per-locale captions per screen; captions render with a user-supplied BMFont zip (font:) — required for non-Latin text, warned when captions exist without one - Guards: Play's 2-screenshot minimum warns, missing raw directories warn with the expected path, byte-level idempotent outputs
- Raw-capture automation (simulator/emulator matrix) starts local-only per the §10.3 decision and is not part of this step
0.1.0-dev.7 #
v2 rebuild, milestone M5a (see V2_PLAN.md §5.1): app icon pipeline.
- New branding setup step (in-house implementation kept per the
§10.2 decision): only source assets live in git
(
branding.icon_src: icon.png, optional fg/bg/mono.png), everything else is regenerated from them - iOS: default
AppIcon.appiconset(15 sizes + Contents.json) via the proven v1 generator - Android: legacy
mipmap-*/ic_launcher.png(5 densities), adaptive icon layers (fg/bg, 108dp base) withmipmap-anydpi-v26/ ic_launcher.xml, and the Android 13+ themed monochrome layer when mono.png exists - Validations that block store rejections early: icon.png must be 1024×1024 with no transparency (App Store rule); fg.png content outside the central 66% adaptive safe area warns
- Byte-level idempotency — unchanged outputs are not rewritten
0.1.0-dev.6 #
v2 rebuild, milestone M4c (see V2_PLAN.md): Developer Portal automation.
- The ios_capabilities step now syncs the Developer Portal via the
official ASC API when the ASC key env vars are set: registers the
bundle ID when missing (POST /v1/bundleIds) and enables declared
capability types (POST /v1/bundleIdCapabilities —
push_notifications → PUSH_NOTIFICATIONS, app_groups → APP_GROUPS).
Idempotent: existing registrations/capabilities are left untouched.
A portal change prints the profile-invalidation warning
(
match --force/ next deploy). Without the ASC key it falls back to the manual guidance AscJwt(ES256, 20-minute validity) +AscApiClientported from the verified v1 implementation, rebuilt on the injectable HttpJsonClient.dart_jsonwebtokendependency restored- Bundle ID lookup compares identifiers exactly (the ASC filter matches substrings — com.x.dev must not shadow com.x)
0.1.0-dev.5 #
v2 rebuild, milestone M4b (see V2_PLAN.md): Setup Kit — firebase + ios_capabilities.
- firebase step (§5.6): creates the Firebase project when missing
(
firebase projects:create), thenflutterfire configure --platforms=android,ios --yesgenerates google-services.json, GoogleService-Info.plist, and lib/firebase_options.dart.firebase.project_idis required;analytics: trueprints the one-time console link (the GA link API needs a GA account choice) - ios_capabilities step (§5.3, local parts): generates/merges
ios/Runner/Runner.entitlementsfromios.capabilities(push_notifications → aps-environment, app_groups → application-groups), wiresCODE_SIGN_ENTITLEMENTSvia the Flutter xcconfig files (no pbxproj surgery), and injectsUIBackgroundModesinto Info.plist. Warns whenremote-notificationlacks the push capability, and points at the manual Developer Portal step (ASC API automation lands in M4c — capability changes invalidate provisioning profiles; regenerate via deploy ormatch --force) - Shared plist text helpers extracted (
PlistText), reused by the admob step
0.1.0-dev.4 #
v2 rebuild, milestone M4a (see V2_PLAN.md): Setup Kit — sentry + admob.
setupis now implemented: idempotent steps driven by the sections in easy_setup.yaml,--only <step>to run one,--dry-runto preview- sentry step (§5.5, fully API-automated): creates the Sentry project
when missing (409 = already exists), resolves the team (
sentry.team, default: the org's first team), fetches the DSN, and writesSENTRY_DSNinto env.json / env.prod.json for--dart-define-from-file. RequiresSENTRY_ORG_TOKEN - admob step (§5.4, Plan B): injects the Android APPLICATION_ID
meta-data, iOS
GADApplicationIdentifier+SKAdNetworkItems, and ad unit IDs asADMOB_<NAME>_<PLATFORM>env keys — env.json gets Google's official test ID whenad_units.<name>.typeis declared (banner | interstitial | rewarded | native | app_open), env.prod.json the real IDs - New schema fields:
sentry.team,admob.ad_units.<name>.type - Remaining M4 scope (firebase, ios capabilities) lands as M4b
0.1.0-dev.3 #
v2 rebuild, milestone M3 (see V2_PLAN.md): Deploy Kit Android + reusable CI workflows.
deploynow deploys Android to Google Play: preflight (doctor checks incl. the Play service account) →flutter build appbundle→fastlane supply(metadata/screenshot upload skipped — that is the M5 pipeline's job)- Play track from
android.play_track_default, overridable per run with--track internal|alpha|beta|production PLAY_SERVICE_ACCOUNT_JSONaccepts a file path or raw JSON (raw JSON is materialized as an ephemeral file for fastlane)- Deploying with both
iosandandroidconfigured runs both platforms in one command - Reusable GitHub Actions workflows shipped in this repo
(
release-ios.yml,release-android.yml,workflow_call): tag push → both stores, secrets inherited from the org/repo initnow also generates the 5-line caller workflow (.github/workflows/release.yml), kept untouched when it already exists
0.1.0-dev.2 #
v2 rebuild, milestone M2 (see V2_PLAN.md): Deploy Kit iOS.
deploynow deploys iOS to TestFlight in one command: preflight (doctor checks) →fastlane match appstore→flutter build ipa(manual signing via a generated ExportOptions.plist pointing at the match App Store profile) →fastlane pilot upload- Version resolution: a
v*git tag at HEAD wins, else the pubspec version; build number from--build-number>GITHUB_RUN_NUMBER> the pubspec+Nsuffix (tag push = release trigger, same code path locally and in CI) - ASC API key (
ASC_KEY_ID/ASC_ISSUER_ID/ASC_KEY_P8[_PATH]) is materialized as an ephemeral fastlane api_key.json for match and pilot, deleted when the deploy finishes --dry-runpreviews the exact commands without executing anything- Android deploy fails fast pointing at milestone M3
0.1.0-dev.1 #
v2 rebuild, milestone M1 (see V2_PLAN.md): CLI skeleton + v2 schema + doctor.
- BREAKING: New v2
easy_setup.yamlschema (app/ios/android/flavors/branding/screenshots/sentry/firebase/admobtop-level sections). The v1easy_setup:root key is detected and reported with migration guidance.flavorandci-cdstill read the v1 schema until they are ported. - BREAKING: Running without a subcommand now prints usage instead of
defaulting to
flavor. - New
initcommand — generates a v2 easy_setup.yaml template (interactive prompts on a terminal) and the asset folder skeleton (assets/branding/icon/,assets/store/screenshots/) - New
doctorcommand — verifies environment tooling (Flutter, Xcode, CocoaPods, Fastlane, ...), project config, and deploy keys/secrets (ASC_KEY_ID/ASC_ISSUER_ID/ASC_KEY_P8[_PATH],MATCH_PASSWORD,PLAY_SERVICE_ACCOUNT_JSON,SENTRY_ORG_TOKEN), with step-by-step issuance guidance for anything missing - New
setup/deploycommands registered as stubs (planned: M4 / M2-M3) - CLI rewritten on
CommandRunnerwith per-command help
0.0.2 #
- Remove unused
app_storemodule (JwtGenerator, AppStoreConnectClient) anddart_jsonwebtokendependency - Remove unused
subtypeparameter and_buildConfigIterationBlockmethod - Translate all code comments, README, and docs to English
- Clarify that
localized/localized_permissionare for non-English locales only (English is the base language) - CI/CD credentials are now configured via
.envfile instead of YAML - Add
repositoryURL to pubspec.yaml - Firebase: Replace file-path copy with FlutterFire CLI (
flutterfire configure) — specifyfirebase.project_idper flavor instead of file paths
0.0.1 #
Flavor Command (default) #
- Android: Auto-configure
build.gradle/build.gradle.ktswithflavorDimensionsandproductFlavors(brace-counting parser for nested Groovy/Kotlin DSL) - iOS (XcodeGen-based): Generate
project.ymland runxcodegen generateto configure Xcode project- Generate per-flavor xcconfig files (Debug/Release/Profile)
- Modify
Info.plistfor flavor-aware display names - Generate/modify
Podfilewith flavor build mode mappings andios_versionsupport - Auto-add
permission_handlerGCC macros to Podfile
- App Icon: Auto-generate all required icon sizes from a single 1024x1024 source image per flavor, with automatic cleanup of unused icons
- Localization: Flavor-specific localized app names via
InfoPlist.stringsand xcconfig variables - Firebase: Configure Firebase per flavor via FlutterFire CLI
- Idempotency: All modifiers/generators are idempotent — safe to run multiple times
- Auto-cleanup: Unused xcconfig files, schemes, and app icons are removed when flavors change
CI/CD Command #
- Fastlane: Generate
.env,Gemfile,Matchfile,Appfile, andFastfilewithsync_certs,build,deploy, andregisterlanes - GitHub Actions: Generate
.github/workflows/ios-deploy.ymlworkflow - App Store Metadata: Generate metadata directory structure for App Store Connect
- Credentials configured via
.envfile (no sensitive data in YAML)
General #
--dry-run/-nflag to preview changes without writing files--project-root/-pflag to specify Flutter project root- Subcommand omission defaults to
flavorfor backward compatibility - User-friendly error messages via
SetupException