Skip to content
Back to archipelago.banuacoder.com

Scaffolding

flutter_modular_monorepo Free ​

Tier
Freeversion 5.2.6
Command
archipelago generate flutter_modular_monorepo
Generates
the Dart workspace, the app, shared packages, devtools and the starter features you choose

A comprehensive Flutter monorepo template with modular architecture, dual-GetIt DI, FeatureSDK self-registration, two-phase initialization, and build-time dependency stripping. Generates a full Dart workspace with Melos, Fastlane, and CI/CD workflows.

Variables ​

VariableTypeDefaultDescription
appNamestringMyAppThe name of your application
organizationstringMyCompanyThe name of your organization
domainstringcomYour organization's top-level domain. See App and Bundle IDs for how they become the Android and iOS IDs.
prefixstringAppPrefix for project components (e.g., 'App' for AppButton)
prefixCasingenum (upperCase, pascalCase)pascalCaseCasing style for the prefix
gitProviderenum (github, gitlab)githubGit provider for CI/CD workflows
networkLibraryenum (dio, http)dioHTTP client library
isUsingWebSocketbooleanfalseInclude WebSocket SDK
websocketLibraryenum (dart, phoenix)dartWebSocket library (if using WebSocket)
isUsingFirebasebooleanfalseUsing Firebase
analyticsVendorsarray[]Analytics vendors to include (firebase, appsflyer, mixpanel, moengage, none)
monitoringVendorsarray[]Monitoring vendors to include (crashlytics, sentry, newrelic, grafana, none)
isUsingFeatureFlagsbooleanfalseInclude Feature Flags SDK
featureFlagVendorenum (flagr, remote_config, custom, none)noneFeature flag vendor implementation
stateManagementenum (bloc, cubit, provider, riverpod)blocState management solution, forwarded as the raw string to the state-aware sub-bricks (shared_dependencies, auth_sdk, home_sdk, paywall_sdk). pre_gen.dart derives only isBloc/isProvider/isRiverpod for this brick's own templates; it never branches on cubit, which is simply passed through.
includeGeneratedbooleanfalseInclude generated files in Git
includeIntegrationTestbooleantrueInclude integration tests
integrationTestFrameworkenum (patrol, maestro, none)patrolIntegration test framework
initializeGitRepobooleantrueInitialize a Git repository
starterFeaturesarrayintroduction_sdk, auth_sdk, home_sdk, paywall_sdkWhich starter feature SDKs to generate. introduction_sdk and home_sdk have real dependencies on auth_sdk (and introduction_sdk on home_sdk too) — see Starter Feature Dependencies below; missing prerequisites are auto-included by pre_gen.dart, not rejected. Any other value (for example a leaf brick such as feedback_sdk) stops generation with Unknown starterFeatures: <values>. Valid values: introduction_sdk, auth_sdk, home_sdk, paywall_sdk. Add other feature bricks after generation with archipelago generate <brick>.
authBackendenum (rest_api, supabase, firebase)rest_apiAuth backend for the auth_sdk starter feature, forwarded to auth_sdk. firebase turns on isUsingFirebase. supabase adds SUPABASE_URL and SUPABASE_ANON_KEY placeholders to every .env.<flavor>. See auth_sdk backend setup. Ignored without auth_sdk.
userTierstringfreeUser subscription tier (injected by CLI, not prompted)

Usage ​

Interactive ​

bash
archipelago generate flutter_modular_monorepo

Non-interactive (CI) ​

bash
archipelago generate flutter_modular_monorepo --config my_config.json

Generation Phases ​

pre_gen.dart stops ("Nothing was changed") when the output directory, or its <app_name> subdirectory, already has an archipelago.yaml: re-creating over a project would record your edits as generated. Add bricks to an existing project with archipelago generate <brick>. At the end of post_gen, the generation records are refreshed only for records this run wrote, and not at all when a sub-brick failed.

post_gen.dart (2,300+ lines) runs a fixed sequence of named phases, logged with === Phase N: ... === banners. Two failure models apply: sub-brick generation failures (Phases 1–5) are hard — any failure there throws at the end and the run is reported as failed; every phase after that (cleanup, workspace pruning, git init, env files, platform init, bootstrap, flavorizr/splash/icons, build_runner, l10n, format) is advisory — a failure is logged as a warning with manual next-step commands, but does not fail the overall generation, because those tools may not be usable immediately after a bare mason make (e.g. flutter/dart not yet bootstrapped).

  1. Phase 0 — writes archipelago.yaml (ArchipelagoConfigHelper.writeInitial, records appName, organization, the Android and iOS IDs (package_name, ios_bundle_id), prefix, stateManagement, vendor selections, etc.) — this is the file ArchipelagoConfigGuard in addon bricks like utilities_sdk requires to already exist. Then: cleans up conditional files (integration-test framework, Firebase files, any empty files left by unmet Mustache conditionals) and copies PNG image assets.
  2. Phase 1: Core Utilities — shared_dependencies, shared_app_config, shared_consent, shared_identifiers, flutter_l10n, core_infrastructure_package, app_lints, app_utilities (all generated via SubBrickGenerator, always isForMonorepo: true)
  3. Phase 1.5: Assets & UI Kit — flutter_asset (with prefix), flutter_ui_kit
  4. Phase 2: Tooling — monorepo_toolkit, asset_generator, changelog_generator, ui_kit_generator
  5. Phase 3: Infrastructure SDKs — network_sdk, launch_tracker_sdk (always; the host bootstrap() and AppDelegate carry its native launch-milestone calls, see Host code), websocket_sdk (if isUsingWebSocket), analytics_sdk + selected vendor impls (if any analytics vendor selected), feature_flag_sdk (if isUsingFeatureFlags), selected monitoring vendor impls, then monitoring_sdk always — even with zero vendors selected, so app_launch_tracker_impl always has monitoring_api to resolve (monitoring_impl.isEnabled is false and the vendor fanout is empty). Monitoring vendor impls generate before monitoring_sdk (the reverse of analytics) — see Vendor Multi-Select below.
    • Pro/Enterprise Tooling (tierToolingBricks in post_gen.dart) — pro or enterprise: coverage_manager, size_guard, detect_new_library, size_analyzer, permission_guard, asset_optimizer; enterprise adds affected. The tiers match brick_registry.yaml. pre_gen.dart derives isPro from userTier, and the CI templates use it: pr-checks.yml, update-baselines.yml and the guard, analysis and baseline jobs of .gitlab-ci.yml render only for Pro and Enterprise, because they call these tools. Before 5.0.0 the CI called detect_new_library, size_analyzer and permission_guard in every tier but no tier generated them, and ui_kit_generator (Free) was generated only for Enterprise.
  6. Phase 5: Starter Features — whichever of introduction_sdk/auth_sdk/home_sdk/paywall_sdk survive dependency-closure expansion (see Starter Feature Dependencies below); an empty starterFeatures list logs "bare scaffold" and generates none. 5.5. Phase 5.5: Workspace Cleanup — pruneEmptyGlobEntries removes any top-level workspace glob entry (in the root pubspec.yaml) that ended up matching zero packages — most commonly features/** on a bare scaffold (starterFeatures: []). Dart's native workspace resolution doesn't skip one dead glob gracefully; it silently degrades package discovery for the entire workspace, breaking melos list/melos bootstrap/melos run build even though dart pub get itself still exits 0.
  7. Phase 6: Git Init (if initializeGitRepo) — git init -b main && git add .
    • Environment files (always, unlabeled in the log banner but internally "Phase 6.5") — writes .env.development/.env.staging/.env.production directly into shared/app_config with a fixed placeholder template (API_BASE_URL, APP_NAME, AES_KEY, DATABASE_URL, plus a line per selected analytics/flagr vendor — kept in sync by hand with shared_app_config's own pre_gen-derived flags), then copies .env.development to the active .env if it doesn't already exist. This is a separate, generation-time mechanism from the env_sync/monorepo_toolkit env commands documented on the shared_app_config page — both exist and both write the same files, so a project generated end-to-end already has working flavor .env files without running env init by hand.
  8. Phase 7: Platform Initialization — runs flutter create in a scratch directory and merges its output into apps/{appName} (android, ios) and the widgetbook app (all platforms) with shared_hooks PlatformScaffoldHelper.ensure, the same path app_scaffold uses. The merge adds only missing files, so the files the brick renders win: MainActivity.kt (FlutterFragmentActivity with the deeplink channels), AppDelegate.swift (deeplinks and the launch_tracker markers), android/app/build.gradle.kts and proguard-rules.pro. flutter create supplies the rest (Runner.xcodeproj, Podfile, icons, Gradle wrapper, AndroidManifest.xml). Info.plist keeps flutter create's UIApplicationSceneManifest: iOS 27 needs the scene lifecycle, and the brick's AppDelegate takes deeplinks as a scene delegate (see Deeplink handling). The phase then patches Gradle (release signing, packaging, ABI splits, artifact names) and iOS config. The Info.plist keys it adds go before the root dict's closing tag, so the scene manifest stays intact. Running it again changes nothing. Before 4.12.1 this phase deleted the brick's platform files and kept plain flutter create output.
    • Phase 7.5: Debug Keystore & Asset Links — runs after Phase 7 so the keystore lands in the merged android/ tree. Generates a debug keystore and substitutes its fingerprint into .well-known/assetlinks.json/apple-app-site-association.
  9. Phase 8: Bootstrap & Code Generation — dart pub get / melos bootstrap.
    • Phase 8.5: Platform & Asset Generation — flavorizr, native splash, launcher icons. flavorizr's instructions: list runs android:flavorizrGradle (emits android/app/flavorizr.gradle.kts with the product flavors) immediately before android:buildGradle (which only appends the apply from("flavorizr.gradle.kts") line referencing it) — listing only the latter leaves a dangling reference and Android builds fail before compiling anything. With Patrol selected, a patrol ios step runs right after flavorizr: PatrolIosHelper adds the RunnerUITests UI testing target (one build configuration per project configuration, so the flavored ones are included), ios/RunnerUITests/RunnerUITests.m and the scheme Test-action entry. The Android side (PatrolJUnitRunner, orchestrator, MainActivityTest.java) is templated, and Patrol's CLI settings are the patrol: section of the app pubspec.yaml. The app's protobuf-java exclusion (a duplicate-class fix) skips the Android test platform's _internal-* configurations, which need that library to run connected tests.
  10. Phase 9: Code Generation — melos run build (build_runner) across the whole workspace.
    • Phase 9.5: L10n Generation — the standalone dart run slang CLI, run after build_runner, not before (melos run build:select filters packages by a direct build_runner dependency, which slang-only packages like shared/locale_core don't declare, so build_runner alone leaves translations.g.dart ungenerated; running slang before build_runner previously caused a different collision).
  11. Phase 10: Format — dart format only. There is no automatic dart fix --apply step — it was deliberately removed (see template_base/CLAUDE.md): on Dart 3.13.0/Flutter 3.47.0, dart fix --apply's prefer_const_constructors_in_immutables and unnecessary_null_checks auto-fixes are confirmed to silently produce invalid/wrong code on this codebase's primary-constructor classes, so it is never run unattended against generated scaffolding.

Vendor Multi-Select ​

Analytics and monitoring vendors are selected via Mason type: array multi-select. Boolean flags are derived in pre_gen.dart:

? Which analytics vendors do you want?
❯ ◉ firebase
  ◯ appsflyer
  ◯ mixpanel
  ◯ moengage
  ◯ none

Only selected vendor bricks are generated. Selecting "none" or leaving empty skips the entire SDK, with one exception: monitoring_sdk itself is always generated (api + impl + noop), even with zero monitoring vendors selected, so app_launch_tracker_impl always has monitoring_api to resolve — with no vendor, monitoring_impl.isEnabled is false and the vendor fanout is empty. build_prepare.yaml does not swap monitoring_impl for the noop: crash reporting ships in release, gated by the diagnostics consent purpose. Monitoring vendor impls also generate before monitoring_sdk (the reverse of analytics, where the SDK generates first) — each vendor registers its own module into injector.dart's externalPackageModulesBefore list, and MonitoringFanoutImpl reads those @Named vendor instances eagerly at MonitoringImplPackageModule.init() time, so a vendor generated afterward would be invisible to the fanout.

Starter Feature Dependencies ​

starterFeatures presents introduction_sdk, auth_sdk, home_sdk, and paywall_sdk as an unconstrained type: array multi-select, but the generated packages are not actually independent of each other:

FeatureRequiresWhy
introduction_sdkauth_sdk, home_sdkGetStartedPage/WelcomePage call GetIt.I<AuthSDK>().requireAuthentication() and navigate via the AuthGuard.authPath constant (both from auth_api); GetStartedPage also calls GetIt.I<HomeSDK>().goToHome(context) (from home_api).
home_sdkauth_sdkHomePage calls GetIt.I<AuthSDK>().logout() (from auth_api).
auth_sdk—No cross-feature package dependency.
paywall_sdk—No cross-feature package dependency — the one starter feature that is genuinely independent and safe to select alone.

These are concrete SDK-interface imports for cross-feature navigation/session control (not just route-name strings), so the coupling is structural, not merely navigational. Decoupling would require introducing shared "capability" interfaces in feature_sdk that auth_sdk/home_sdk implement and introduction_sdk/home_sdk depend on instead of the concrete auth_api/home_api packages — a larger redesign that hasn't been justified yet.

Instead, flutter_modular_monorepo/hooks/pre_gen.dart expands whatever starterFeatures subset is selected to its full dependency closure before Phase 5 runs, logging a notice (starterFeatures: auto-including auth_sdk, home_sdk (required by introduction_sdk)) whenever it adds a prerequisite the user didn't explicitly select. This means every starterFeatures subset — including [], ["paywall_sdk"], ["home_sdk"], or ["introduction_sdk"] alone — always generates a workspace that resolves cleanly; omitted prerequisites are silently (but visibly, via the log line) added rather than left to break dart pub get.

Mason does not check the values of an array var, so pre_gen.dart does: an unknown starterFeatures value logs an error that names the valid values and exits 1, instead of being dropped.

Generated Structure ​

text
your_project/
├── .claude/commands/               # Claude Code slash commands
├── .github/workflows/              # CI/CD workflows (if GitHub)
├── .claude/skills/                 # Agent skills (see AI Agent Support)
├── .cursorrules                    # Cursor rules
├── AGENTS.md                       # AI agent context + skill index
├── CLAUDE.md                       # AI coding assistant context
├── .mcp.json                       # Registers the Marionette MCP server
├── .run/                           # JetBrains run configurations, per app and flavor
├── .vscode/                        # launch.json + tasks.json, per app and flavor
├── apps/
│   ├── {app_name}/
│   │   ├── android/                # Brick host files + flutter create backfill + Gradle patching
│   │   ├── ios/                    # Brick AppDelegate + flutter create backfill + iOS patching
│   │   ├── fastlane/               # iOS/Android build automation
│   │   ├── launcher/               # App icons per flavor
│   │   ├── splash/                 # Splash screen assets
│   │   ├── lib/
│   │   │   ├── app.dart
│   │   │   ├── bootstrap.dart
│   │   │   ├── main_development.dart
│   │   │   ├── main_staging.dart
│   │   │   ├── main_production.dart
│   │   │   ├── di/injector.dart
│   │   │   ├── host/
│   │   │   │   ├── host_app_widget.dart
│   │   │   │   └── initializers/
│   │   │   └── router/app_router.dart
│   │   ├── test/
│   │   │   ├── architecture/feature_boundary_test.dart  # No feature depends on another's _impl
│   │   │   ├── di/injector_test.dart   # Composition-root smoke test
│   │   │   ├── di/{lazy_feature_sdk,base_scope_budget,port_singleton}_test.dart  # DI guard tests
│   │   │   └── support/composition_root_harness.dart, feature_files.dart  # Harness filled by leaf bricks
│   │   ├── flavorizr.yaml
│   │   ├── flutter_launcher_icons-*.yaml
│   │   └── flutter_native_splash.yaml
│   └── {app_name}_widgetbook/      # Widgetbook app (all platforms)
├── shared/
│   ├── feature_sdk/                # Native to this brick — FeatureSDK base + registry
│   ├── router_registry/            # Native to this brick — route registration abstraction
│   ├── consent_api/                # Generated by the `shared_consent` sub-brick (Phase 1) — telemetry consent + PII-scrub contracts
│   ├── consent_impl/               # Generated by the `shared_consent` sub-brick (Phase 1) — persisted consent gate, default scrub rules
│   ├── identifiers/                # Generated by the `shared_identifiers` sub-brick (Phase 1)
│   ├── locale_core/                # Generated by the `flutter_l10n` sub-brick (Phase 1)
│   ├── dependencies/               # Generated by the `shared_dependencies` sub-brick (Phase 1)
│   └── app_config/                 # Generated by the `shared_app_config` sub-brick (Phase 1)
├── features/                       # Empty except .gitkeep until Phase 5 sub-bricks populate it
│   ├── auth/                       # auth_api + auth_impl (with per-feature slang)
│   ├── home/                       # home_api + home_impl (with per-feature slang)
│   ├── introduction/               # Onboarding feature
│   └── paywall/                    # paywall_api + paywall_impl
├── infrastructure/                 # Infrastructure SDKs + vendor impls (Phase 3 sub-bricks)
├── packages/                       # UI Kit, shared packages (Phase 1.5 sub-bricks)
├── utilities/                      # Utility packages (Phase 1 sub-bricks)
├── devtools/                       # Monorepo toolkit + asset generator (Phase 2 sub-bricks)
├── gradle/libs.versions.toml       # Native to this brick — Gradle version catalog
├── third_party/                    # Native to this brick — vendored upstream patches
│   └── mason_logger/               # In-tree patched copy resolving a win32 6.x/flutter_flavorizr
│                                   # compile-time conflict; see its own
│                                   # PROVENANCE.md for the upstream commit and removal condition
├── .well-known/
│   ├── assetlinks.json             # Android App Links — fingerprint substituted in Phase 7.5
│   └── apple-app-site-association  # iOS Universal Links — same substitution
├── build_prepare.yaml              # Build-time dependency stripping
├── flavors.yaml                    # Flavor configuration
├── pubspec.yaml                    # Workspace root with melos scripts
└── analysis_options.yaml

Key Architecture Patterns ​

  • Dual-GetIt — Global scope (SDKs, infra) + local scope per feature (repos, usecases)
  • FeatureSDK self-registration — Features register routes and DI without app knowing about them
  • Two-phase init — Pre-launch (blocking: DI, routes) + Post-launch (non-blocking: analytics, flags)
  • build_prepare — Swap debug → noop dependencies at build time (debug tooling such as app_debugger → app_debugger_noop; monitoring and the launch tracker are not swapped). The debug_build_only list follows the build mode: --mode release|profile swaps app_marionette → app_marionette_noop for every flavor
  • Marionette MCP — features/app_marionette installs LeanCode's MarionetteBinding in debug builds only, so an AI agent can drive the running app; the Fastfile, CI templates and melos scripts pass --mode so no release or profile build contains it (see AI Agent Support)
  • Composition-root smoke test: apps/{app_name}/test/di/injector_test.dart builds the real DI container under flutter test. Leaf bricks add their test prerequisites to test/support/composition_root_harness.dart when they register their DI module. melos run di:check fails when that harness or injector.dart drifts from the package manifests. See Testing.
  • DI guard tests: test/architecture/feature_boundary_test.dart, test/di/lazy_feature_sdk_test.dart, test/di/base_scope_budget_test.dart and test/di/port_singleton_test.dart guard the feature boundary, lazy SDKs, the boot budget and port identity without a per-feature list. See DI Guard Tests.
  • Per-feature slang — Each feature module can have its own translations with scoped translate_var

Note: userTier is injected automatically by the CLI based on the authenticated user's subscription tier and is not included in interactive prompts.

Test gate and golden tests ​

The root pubspec.yaml melos scripts test:select (and so test) and coverage:run:parallel run flutter test --coverage --exclude-tags golden. They exclude golden tests by tag, so the *_ui_kit package keeps its ordinary tests in the default run. flutter test exits 79 when the exclusion leaves a package with no tests (all tests golden). The scripts accept 79 as success; any other exit code fails.

melos run test:golden runs only the golden tests, read-only against the committed fixtures. To regenerate fixtures, run flutter test --tags golden --update-goldens inside the UI kit package. See the testing guide.

Android Release Build ​

android/app/build.gradle.kts's release build type ships with isMinifyEnabled/isShrinkResources on and proguardFiles(...) wired to android/app/proguard-rules.pro. That file carries baseline @Keep-respecting and stack-trace-preserving rules, plus an archipelago:proguard:keep-rules marker pair — capability bricks (webview_sdk, webview_sdk_advanced, biometric_auth_sdk) append their own conditional keep rules there at generation time, so a release build only carries the rules the app's actual dependency tree needs.

An archiveDebugSymbols Gradle task is also registered:

bash
./gradlew :app:archiveDebugSymbols -Pvariant=release

Run after a release build — copies that variant's R8 mapping.txt and native-debug-symbols bundle into build/outputs/debug-artifacts/<variant>/, a stable path CI or a developer can upload to a crash-reporting vendor for deobfuscation.

Changelog ​

Run archipelago brick info flutter_modular_monorepo to see the latest published version. Enterprise customers can read the full CHANGELOG.md in the brick source repository.