Architecture
Monorepo Toolkit
The monorepo toolkit is a collection of CLI scripts generated into your project at devtools/scripts/, wired together by the monorepo_toolkit CLI at devtools/monorepo_toolkit/. These tools help you manage a large Flutter monorepo — from dependency analysis to build preparation to asset optimization.
Every command runs from your project root. Most also have a melos shortcut:
dart run monorepo_toolkit <command> [subcommand] [flags]
melos run <command>:<subcommand> # shortcut, e.g. melos run graph:cyclesExtra arguments go after -- with melos, for example melos run env:switch -- production. Pass --verbose for noisy logging and --version to print the toolkit version.
Which scripts you get depends on the tier you generate with: Free projects include the core scripts, Asset Generator and the UI Kit Manager. Pro adds Coverage, Size Guard, Asset Optimizer, Size Analyzer, Permission Guard and Detect New Library. Enterprise adds Affected. The generated CI calls only the scripts your tier has: Pro and Enterprise projects get pr-checks.yml and update-baselines.yml (GitHub) or the guard, analysis and baseline jobs in .gitlab-ci.yml (GitLab). Every tier gets the test workflow (ci.yml on GitHub, the test job on GitLab) and the build workflows. A melos shortcut for a script you did not generate will fail until its brick is installed.
Upgrading a Free project to Pro
Run archipelago generate for each of size_guard, detect_new_library, size_analyzer and permission_guard. When the last of the four is installed, the generator adds the Pro CI: it writes .github/workflows/pr-checks.yml and update-baselines.yml if they are missing (GitHub), or appends the guard, analysis and baseline jobs to .gitlab-ci.yml between # archipelago:pro_ci:start and # archipelago:pro_ci:end (GitLab). The CI provider comes from ci.git_provider in archipelago.yaml. The workflows call all four tools, so nothing is added before the fourth is installed. Existing files are never overwritten, so running the commands again changes nothing.
CI test scope
The generated CI runs only what a change can break, and runs it once per change. The same rules drive Archipelago's own CI.
| Event | GitHub Actions | GitLab CI | What runs |
|---|---|---|---|
PR/MR into develop | pull_request | merge_request_event | Analyze and test the changed packages plus their dependents. Pro: size guard and new-library check |
Direct push to develop | push (develop only) | branch pipeline (develop only) | Same, diffed against the last commit ci.yml passed on develop (GitLab: the push's before commit) |
Push of a merged PR/MR to develop | push | branch pipeline | Nothing: the PR/MR run tested it. GitHub asks the API whether the head commit belongs to a merged PR; GitLab looks for the "See merge request" line of its default merge commit message, so squash and fast-forward merges are tested again |
PR/MR into main, or from release/* / hotfix/* | pull_request | merge_request_event | Every package. Pro: also the APK/IPA builds with size analysis and permission guard |
| Manual run | workflow_dispatch | web pipeline | Everything. On develop, GitLab's manual pipeline also refreshes the baselines |
Push to main, to a PR/MR branch, to release/* / hotfix/* | none | no pipeline | Nothing: the PR/MR run already covered the change |
Tag mobile/v* | release-mobile.yml (release builds, no tests) | no pipeline | GitLab has no release job: run Fastlane from your release process |
devtools/ci/scope.shdecides the scope (full,base,version_only,code). It needs the full history (fetch-depth: 0,GIT_DEPTH: 0), because a shallow clone has no merge base. Run it locally withCI_SCOPE_BASE=<ref> bash devtools/ci/scope.sh. On GitHub it prints a warning when it cannot find the last passing run (first run, or an API error) and falls back to the push's before commit.- A change to a workspace-wide file runs everything: root
pubspec.yaml/pubspec.lock,analysis_options.yaml,build_prepare.yaml,flavors.yaml,devtools/ci/,.github/workflows/,.gitlab-ci.yml. No package owns these files, so an affected-only run would test nothing. devtools/ci/test.shbootstraps, generates code, then runsmelos run analyzeandmelos run teston a full run. Otherwise it usesmonorepo_toolkit affected analyze --no-fatal-infos --no-fatal-warningsandaffected testwith--base <base>when the project has the Enterpriseaffectedtool, andmelos exec --diff=<base> --include-dependentswhen it does not. A package withoutlib/ortest/is skipped inside the command; melos's--dir-existsfilter is not used, because melos applies it before--include-dependentsand would drop the dependents of a changed package that has no tests.- A change that only bumps
version:inpubspec.yamlfiles and editsCHANGELOG.md, an empty back-merge, and a docs-only change (*.md,docs/) skip the suite. - Jobs are gated with job-level
if:(GitHub) andrules:(GitLab), not workflowpaths:filters. GitHub readspaths:from the first 300 changed files only, and a workflow skipped bypaths:leaves a required check pending forever. - Require
ci-okin branch protection (GitHub). A job skipped byif:reports success, so ifscopefailed, the skippedtestjob would count as passing;ci-okfails unlessscopepassed andtestpassed or was skipped on purpose. On GitLab thetestjob runs the scope itself and fails when it fails. - Test workflows cancel the older run of the same PR or branch:
concurrencywithcancel-in-progress: trueon GitHub;workflow:auto_cancel:on_new_commit: interruptibleon GitLab, where only MR test jobs areinterruptible(rules:interruptible). A GitLab develop push pipeline is never cancelled, as it diffs only from the commit before its own push. The GitLab file needs GitLab 16.10 or later. Release, deploy and baseline jobs never cancel. - Heavy jobs are release-only or manual: the Pro APK/IPA size and permission checks run on release PRs/MRs and manual runs, and
update-baselines.ymlruns only by hand (run it ondevelopafter a release). Goldens stay out of CI (melos run test:golden).
The workflows are self-contained and do not call the shared banua-coder/banua-coder-workflow reusable workflows, which offer the same scope options for other repos (2026-10-15 amendment).
Projects generated before flutter_modular_monorepo 5.1.0 have no ci.yml/test job and run the Pro checks on every PR. To adopt the scope, copy devtools/ci/, .github/workflows/ci.yml, pr-checks.yml and update-baselines.yml (or .gitlab-ci.yml) from a fresh project (archipelago create --output <scratch dir>).
TIP
The toolkit is generated into your project so it works in CI without requiring the Archipelago CLI at build time. You own the code and can customize it.
Detailed Guides
Each script has a dedicated guide with config formats, examples, and CI integration:
Core Scripts (Free):
- Run App — Interactive flavor and device selection
- Build Prepare — Debug/release dependency swapping
- Environment Sync — .env management across flavors
- Flavor Manager — Flavor configuration (dev/staging/production)
- Dependency Graph — Visualization and enforcement
- Dependency Manager — Centralized version management
- Workspace Setup — Workspace initialization
- App Scaffold — Add new apps to workspace
- Asset Generator — Type-safe asset enums
- Changelog — CHANGELOG.md entry grouped by commit type
- UI Kit Manager — Component and theme management
Extension Scripts (Pro/Enterprise):
- Affected Packages — Git-based change detection Enterprise
- Test Coverage — Coverage pipeline with thresholds Pro
- Size Guard — File size enforcement Pro
- Asset Optimizer — SVG/PNG/JPEG optimization Pro
- Size Analyzer — App size breakdown and baseline diff Pro
- Permission Guard — Compiled-artifact permission diff Pro
- Detect New Library — Report newly added dependencies in a PR Pro
affected
Detect which packages changed since the last release and run targeted operations only on affected packages.
affected list
List packages affected by changes since the base branch (--base, default main).
melos run affected:list
dart run monorepo_toolkit affected list --base develop --jsonaffected analyze
Run static analysis only on affected packages.
melos run affected:analyzeaffected test
Run tests only on affected packages.
melos run affected:testaffected run
Run an arbitrary shell command only on affected packages.
melos run affected:run -- "dart format --set-exit-if-changed ."Other affected subcommands
affected format, affected fix-check, affected golden, affected build (app packages only, --platform apk|aab|ios|ipa), affected license and affected devtool (size-guard, dependency-graph, coverage) have no melos shortcut. Call them with dart run monorepo_toolkit affected <subcommand>. See Affected Packages for flags.
TIP
Use affected in CI to skip unchanged packages and reduce pipeline time. It compares against the merge base of your PR branch.
build-prepare
Swap debug tooling for its no-op twins in production-flavor builds, or restore it. The build flavor decides: --flavor <f> keeps the tooling when <f> is in internal_flavors and swaps it otherwise. Flags: --flavor/-f, --check-leaks (after a build, fail if a debug-only package resolved), --restore/-r (revert the last swap from .build_prepare.state.json) and --config/-c (default build_prepare.yaml). release and debug stay as explicit-mode aliases.
build-prepare --flavor <flavor>
Prepare dependencies for a build of that flavor. Internal flavors keep the debug tooling; every other flavor gets the noop twins.
dart run monorepo_toolkit build-prepare --flavor production
melos run build-prepare:production # same; build-prepare:release is an aliasbuild-prepare --check-leaks --flavor <flavor>
Run after the build. Fails a production-flavor build that still resolves a mapped debug package or a debug_only package.
build-prepare --restore / build-prepare debug
Restore the debug/development dependencies.
melos run build-prepare:debugHow it works
Reads build_prepare.yaml at the workspace root, swaps the package names in the pubspec.yaml files under target_paths, and rewrites the matching package: imports in their Dart files. Debug and noop packages export the same class names, so the app compiles with either.
# build_prepare.yaml
build_prepare:
internal_flavors: [development, staging, alpha, beta]
mappings:
- debug: app_debugger
release: app_debugger_noop
target_paths:
- apps/your_appSee Build Prepare for the full format.
WARNING
Always restore (build-prepare --restore) after a production build, and never run the production swap before an internal build. The Fastlane integration handles both automatically.
coverage
Manage test coverage across the monorepo.
coverage init
Initialize coverage configuration for all packages.
melos run coverage:initcoverage run
Run tests with coverage collection.
melos run coverage:runcoverage combine
Combine coverage reports from all packages into a single report.
melos run coverage:combinecoverage check
Check that coverage meets the minimum threshold.
melos run coverage:checkThresholds come from coverage.yaml at the workspace root (min_line_threshold, min_branch_threshold, min_function_threshold, with per-package overrides). coverage check takes no flags. See Test Coverage.
coverage full
Run the full coverage pipeline: test, combine, and check. Flags: --concurrency/-j (default 1) and --[no-]fail-fast.
melos run coverage:full
dart run monorepo_toolkit coverage full -j 4There is also melos run coverage:run:parallel, which runs flutter test --coverage in 4 packages at once.
TIP
Use coverage full in CI as a single command that runs tests, combines reports, and fails the build if coverage drops below the threshold.
deps
Manage dependencies across the monorepo.
deps add
Add a pub.dev dependency to the centralized shared/dependencies package, optionally re-exporting it from a barrel file.
melos run deps:add -- freezed_annotation -v "^2.4.0" -c modelsFlags: --version/-v, --category/-c (barrel file).
deps remove
Remove a dependency from shared/dependencies and its barrel exports.
melos run deps:remove -- freezed_annotation --forceFlag: --force/-f skips the confirmation prompt.
deps internal
Add one workspace package as a dependency of another (the relative path is computed for you).
melos run deps:internal -- <source-package> <target-package>deps external
Add a pub.dev dependency directly to one package.
melos run deps:external -- <package-name> <dependency> -v "^5.4.0" --devFlags: --version/-v, --dev/-d. See Dependency Manager.
deps check, deps align, deps diagnose
Find direct pins that drift between packages or duplicate the barrels (check), set one constraint everywhere (align <package> [constraint]), and explain a pub get version-solving failure (diagnose, --write-overrides is opt-in).
melos run deps:check
melos run deps:align -- dio
melos run deps:diagnoseenv
Manage environment variables and flavor configurations.
env init
Initialize environment configuration for the project. Flags: --var/-v (repeatable) and --force.
melos run env:initenv add
Add a new environment variable to every flavor's .env file and to env.dart.
melos run env:add -- API_SECRETenv sync
Sync the flavor enum with the current flavors.yaml configuration.
melos run env:syncenv switch
Copy .env.<flavor> to .env so build_runner picks up the correct secrets for a flavor. Run this before every build. --if-missing only writes .env when it does not exist yet (this is what melos run env:ensure runs after every melos bootstrap).
melos run env:switch -- development
melos run env:switch -- production
# Or invoke the toolkit directly:
dart run monorepo_toolkit env switch developmentBuild workflow
melos run env:switch -- development
dart run build_runner build --delete-conflicting-outputs
flutter build apk --flavor developmentSee the Environment Sync guide for CI integration and the full command reference.
Flavor workflow
- Define flavors in
flavors.yamlat the workspace root - Run
env:syncto generate the Dart enum - Each flavor has its own
.env.<flavor>file (.env.development,.env.staging,.env.production) - Run
env switch <flavor>to copy the active flavor to.env - Run
build_runner buildto generate obfuscated Dart code from.env
flavor
Manage app flavors (variants like dev, staging, production).
flavor list-apps
List all apps in the workspace that support flavors.
melos run flavor:list-appsflavor list
List all defined flavors for an app.
melos run flavor:list -- --app my_appflavor add
Add a new flavor to an app.
melos run flavor:add -- --app my_app --name qaFlags: --app/-a and --name/-n (required), --app-name, --suffix/-s, --generate/-g.
flavor generate
Generate native flavor files (flutter_flavorizr), launcher icons, splash screens, IDE run configurations and main_<flavor>.dart entry points.
melos run flavor:generate -- --app my_appFlags: --app/-a (required), --force/-f, --check, and --no-splash, --no-icons, --no-ide-configs, --no-main-files.
flavor sync
Sync flavor definitions between the global flavors.yaml and the apps.
melos run flavor:syncFlags: --app/-a, --from-apps, --init.
graph
Analyze and visualize the dependency graph of the monorepo.
graph visualize
Generate a visual dependency graph. --format/-f is text (default), dot, mermaid or json; --output/-o writes to a file.
melos run graph:visualize -- --format mermaid -o graph.mdgraph stats
Show dependency statistics (counts, depth, fan-in/fan-out).
melos run graph:statsgraph cycles
Detect circular dependencies in the workspace. Exits non-zero when cycles exist, unless --no-fail is passed.
melos run graph:cyclesWARNING
Circular dependencies break the layered architecture. Run graph:cycles in CI to prevent them from being introduced.
graph validate
Validate the dependency graph against architecture rules (layer violations, forbidden dependencies).
melos run graph:validatesize-guard
Monitor and enforce app binary size budgets.
size-guard check
Scan the workspace for files above a size threshold (default 500KB), such as unoptimized images, fonts or videos.
melos run size-guard:check
dart run monorepo_toolkit size-guard check --changed-only --base developFlags: --threshold/-t, --extension/-e, --ignore/-i, --config/-c (default size_guard.yaml), --stats/-s, --changed-only, --base/-b.
TIP
Use --changed-only as a PR gate so it only flags files the branch introduced. See Size Guard.
size-analyzer
Parse the JSON from flutter build --analyze-size, list the top size contributors and diff against a committed baseline. No melos shortcut.
dart run monorepo_toolkit size-analyzer analyze --json build/app-size.json --baseline size_baseline/android.json
dart run monorepo_toolkit size-analyzer update-baseline --json build/app-size.json --output size_baseline/android.jsonSee Size Analyzer.
permission-guard
Extract permissions from a compiled APK or IPA and diff them against a committed baseline. No melos shortcut.
dart run monorepo_toolkit permission-guard android --apk build/app/outputs/flutter-apk/app-release.apk
dart run monorepo_toolkit permission-guard ios --ipa build/ios/ipa/MyApp.ipa
dart run monorepo_toolkit permission-guard update-baseline --platform android --artifact app-release.apkSee Permission Guard.
detect-new-library
Report direct dependencies added to pubspec.yaml files in a PR (warn-only unless --fail or fail_on_new: true). No melos shortcut.
dart run monorepo_toolkit detect-new-library check --base developSee Detect New Library.
assets
Manage and optimize image and media assets.
assets optimize
Optimize SVG, PNG and JPEG assets using svgo, oxipng and jpegoptim (install them first). Flags: --dry-run/-d, --config/-c.
melos run assets:optimizechangelog
Write a CHANGELOG.md section from the commits since the last matching tag, grouped by Conventional Commit type. Flags: --package, --tag-pattern, --version, --all, --dry-run/-d. See Changelog.
melos run changelog -- --version 1.4.0asset-gen
Generate Dart constants for assets.
asset-gen generate
Generate type-safe asset enums from files in the asset package's assets/ directory. Flags: --dry-run/-d, --package/-p, --prefix.
melos run asset-gen:generateExample output
// Generated — do not modify
class AppAssets {
static const String logo = 'assets/images/logo.png';
static const String onboarding1 = 'assets/images/onboarding_1.svg';
}ui-kit
Manage UI kit components in the design system package.
ui-kit list
List all registered UI kit components.
melos run ui-kit:listui-kit add
Add a new component to the UI kit.
melos run ui-kit:addui-kit remove
Remove a component from the UI kit.
melos run ui-kit:removeui-kit status
Show which components are installed and how many are available.
melos run ui-kit:statusui-kit create
Scaffold a new custom component (--name, --layer, --dependency) and register it.
melos run ui-kit:createtheme-variant
Generate theme variant files from a YAML config (--config/-c required, --prefix, --ui-kit-path).
melos run theme-variant -- --config theme_variants.yamlworkspace
Workspace-level setup and configuration.
workspace setup
One-time migration of the root pubspec.yaml workspace list from explicit paths to Dart 3.11+ glob patterns. Does nothing if globs are already in use. --dry-run/-n previews.
melos run workspace:setupworkspace fix-resolution
Add the missing resolution: workspace field to every workspace package. --dry-run/-n previews.
dart run monorepo_toolkit workspace fix-resolutionworkspace validate
Check resolution: workspace on all packages and detect duplicate dependency_overrides. Exits 1 on problems, so it is CI-safe.
dart run monorepo_toolkit workspace validateTIP
To set up a fresh clone, run melos bootstrap. It also seeds .env through the env:ensure hook.
app
Manage app targets in the monorepo.
app create
Scaffold a new app target in the apps/ directory.
melos run app:create -- --name my_app --org com.exampleFlags: --name/-n (required, snake_case), --org/-o (default com.example), --domain/-d.
run
Run an app with interactive app, flavor and device selection. Flags: --app/-a, --flavor/-f, --device/-d, --no-prepare.
melos run run:appSee Run App.
prepare
Prepare the workspace for a flavor in one idempotent step: env switch, envied regeneration and build-prepare. run calls it for you.
dart run monorepo_toolkit prepare --flavor productionFlags: --flavor/-f (required), --mode/-m (auto default: the flavor decides, internal flavors keep the debug tooling and every other flavor gets the noop twins; debug or release force a direction), --app-config-path.
generate host-registry
Regenerate the marker regions of an app's injector.dart from each package's archipelago: di_modules manifest.
melos run di:check # same as: generate host-registry --check
dart run monorepo_toolkit generate host-registry --app my_appFlags: --app/-a, --target/-t, --root, --prune, --check (report drift without writing, exit 1 if it would change).
# …// archipelago:feature_imports:start// …import 'package:autoscroll_sdk_impl/autoscroll_sdk_impl.dart';import 'package:connectivity_impl/connectivity_impl.dart';// …// archipelago:feature_imports:end@InjectableInit( externalPackageModulesBefore: [ // archipelago:feature_modules_before:start // … ExternalModule(AuthImplGlobalModule), ExternalModule(FeedbackImplPackageModule), // … // archipelago:feature_modules_before:end ],generate test-harness
Write the app's test/support/composition_root_harness.dart from each package's archipelago: test_prerequisites declaration. The file gives tests one call, installCompositionRootPrerequisites(), that installs every platform fake the installed packages need before initializeInjector() can run under flutter test. See Testing.
dart run monorepo_toolkit generate test-harness
dart run monorepo_toolkit generate test-harness --checkIf the file is missing, the command creates it. If the file exists, the command rewrites only the archipelago:test_prerequisite_imports and archipelago:test_prerequisites marker regions. The command fails when no app has a lib/di/injector.dart, or when the harness exists but has lost a marker pair.
Commit the harness. It is not build_runner output, so nothing regenerates it on a fresh checkout. Run the command again after you add or remove a package that declares a prerequisite.
Flags: --app/-a, --root, --check (report drift without writing, exit 1 if it would change).
miniapp_signer (separate executable) Pro
Signs mini-app manifest v2 files. The webview_sdk_advanced brick generates it at devtools/scripts/miniapp_signer/. It is not a monorepo_toolkit subcommand, so projects without that brick do not get it or its cryptography dependency.
dart run miniapp_signer keygen --kid 2026-a --out ~/.miniapp-keys/2026-a.jwk.json
dart run miniapp_signer sign --key ~/.miniapp-keys/2026-a.jwk.json --kid 2026-a rewards.json -o rewards.jws
dart run miniapp_signer verify --keys apps/my_app/assets/config/mini_app_registry_keys.json rewards.jwskeygen refuses an --out path inside a git work tree unless you pass --force. Exit codes: 0 OK, 1 refused or invalid, 64 usage error. See Where to put signing keys.
Quick Reference
| Command | Description |
|---|---|
affected:list | List changed packages |
affected:test | Test changed packages only |
build-prepare:production | Swap debug tooling to noop for the production flavor (build-prepare:release is an alias) |
build-prepare:debug | Restore debug deps |
coverage:full | Full coverage pipeline |
graph:cycles | Detect circular deps |
graph:validate | Validate architecture rules |
workspace:setup | Migrate workspace list to globs |
env:sync | Sync flavors |
env:switch -- <flavor> | Select active env flavor before build |
run:app | Run an app with flavor and device selection |
di:check | Fail if injector.dart is out of date |
size-guard:check | Flag oversized files |
assets:optimize | Optimize images |
asset-gen:generate | Generate asset enums |
changelog | Write a CHANGELOG.md entry from commits since the last tag |