The modular CLI, and exactly what it does when you run it.

modular ships from its own repo, modular_cli — a self-contained Dart package with its own resolution and its own tests, versioned separately from the template. Four commands, no code generation magic: it reads and writes plain text at known anchor points.

scaffold + wire

new feature <name>

Generates an _api/_impl pair and registers it — in the workspace, the DI config, and the router.

verify

doctor

Five architecture checks the Dart analyzer has no way to run.

codegen

gen assets

Regenerates AppVectorAssets / AppRasterAssets from what's on disk.

rebrand

rename

Rewrites the bundle id and app name across every platform folder.

modular new feature <name>

Generating packages is easy. The wiring is the point.

A feature that compiles but was never registered fails at runtime, the first time something resolves its facade. So the command doesn't stop at writing files — it edits three other files for you, at anchors that already exist in a fresh checkout.

What happens, in order

1

Validate the name

Naming.fromFeatureName requires lower_snake_case (^[a-z][a-z0-9]*(_[a-z0-9]+)*$) and rejects Dart reserved words — rejecting rather than normalizing keeps the package name, directory and generated class names in agreement.

2

Locate the workspace

Project.locate() walks up from the working directory looking for a pubspec.yaml that declares workspace: — so the CLI behaves the same from any subdirectory.

3

Verify the anchors exist first

Before writing a single file, it checks all three anchor comments are present. A missing anchor fails loudly here — never as a half-generated feature on disk.

4

Render the templates

{{name}} / {{Name}} / {{camelName}} are substituted, then the package: import block is re-sorted — whether {{name}}_api sorts before router_api depends on the name, so it can never be hardcoded in a template.

5

Write features/<name>/

13 files: two pubspecs, two barrels, the API facade + launcher, the impl facade + launcher, the module, the controller, the screen, the route info and the module router.

6

Wire it in

Three edits — see the anchor system below — to the root pubspec.yaml, dependency_injection_configuration.dart and (unless --route=false) router_configuration.dart.

~/modular_app_template
--dry-run

The anchor system

Comments that survive reformatting, and fail loudly if removed.

Rather than parsing Dart or YAML, the wiring step looks for a plain text marker and inserts a line above it, matching that line's indentation. Simple enough to never break on a hand edit — and if you delete the anchor, the next new feature tells you exactly why it stopped.

pubspec.yaml

  # feature modules
  - features/counter/*
+ - features/user_profile/*
  # <generated:feature-packages>

dependency_injection_configuration.dart

+ import 'package:user_profile_impl/
+   user_profile_impl.dart';
  ...
  CounterModule(),
+ UserProfileModule(),
  // <generated:feature-modules>

router_configuration.dart

+ import 'package:user_profile_impl/
+   user_profile_impl.dart';
  ...
  CounterModuleRouter(),
+ UserProfileModuleRouter(),
  // <generated:feature-routers>

AnchoredFile.insertImport does the same for the import line itself — it walks the existing import 'package:...' block and inserts alphabetically, so the result satisfies directives_ordering without a manual pass.

modular doctor

Five checks the compiler has no way to run.

implementation_imports: error already stops an _impl internal from being imported directly. What it can't see is a feature that was never registered, or an _api package that quietly grew a dependency on an implementation — both fail at runtime, or not until someone tries to reuse the package.

api-depends-on-impl

An _api package must not depend on any _impl package — that would make the contract require its own implementation to even compile.

impl-to-impl

Only app may depend on an _impl package. Everything else — including another feature — must go through the _api contract.

not-in-workspace

A package that declares resolution: workspace but is missing from the root pubspec.yaml's workspace: list resolves separately and silently drifts from the rest of the repo.

module-not-registered

The failure this command exists for: a DependencyModule implementation that's never passed to DependencyInjectionConfiguration — compiles fine, throws the first time something resolves it.

route-info-exported

A _route_info.dart file exported from an _impl barrel lets other modules navigate by hardcoded path, bypassing the launcher entirely.

modular doctor

modular gen assets

Typed accessors, generated from what's actually on disk.

Drop a file into core/design_system/assets/{vectors,rasters}/, run the command, and it's available as AppVectorAssets.yourFile — never a raw string path. The file name is turned into a Dart identifier (arrow-right.svgarrowRight), and a name that can't become one — a Dart reserved word, an empty stem — fails the command instead of emitting code that won't compile.

CI runs gen assets --check and fails if the committed output doesn't match what the generator would produce — the generated files are committed, and this is what keeps them honest.

generated by `modular gen assets`

// GENERATED — DO NOT EDIT BY HAND.
abstract class AppVectorAssets {
  static const arrowRight = AppVectorAsset(
    path: 'vectors/arrow-right.svg',
    packageName: 'assets',
  );
}

modular rename

One command, every platform folder.

Renaming a Flutter app by hand means touching the Android applicationId, the iOS bundle identifier, both platforms' display names, and every leftover reference to the template's own name. modular rename does the pass once, from the CLI, right after cloning.

terminal

modular rename \
  --bundle-id com.acme.myapp \
  --name "My App"
Why the CLI lives in its own repo It's a separate Dart package with its own dart pub get, its own analysis_options.yaml and its own tests — deliberately isolated so the CLI's dependencies (args, path) never leak into the app's resolution, and so it can be released and versioned on its own schedule, activated globally (dart pub global activate --source git https://github.com/Yusubov-Engineering/modular_cli.git --git-ref v1.0.0) independent of whichever project it's pointed at.