clean_architect 0.9.0
clean_architect: ^0.9.0 copied to clipboard
A configurable CLI generator for clean architecture Flutter and Dart feature modules.
clean_architect #
A configurable Dart CLI generator for Clean Architecture Flutter/Dart projects.
clean_architect generates boring, predictable architecture files that you can edit immediately. The generator does not try to hide your app behind runtime abstractions. The flexibility lives in clean_architect.yaml: paths, layer layout, state management, network client, local storage, model style, and dependency injection style.
Install #
dart pub global activate clean_architect
After global activation you can run the executable from any folder:
clean_architect init
clean_architect create architecture
clean_architect create auth
clean_architect create feature orders
Empty Folder Usage #
Use the global executable when generating into an empty folder:
mkdir my_app
cd my_app
clean_architect create architecture
Do not use dart run clean_architect ... in an empty folder. dart run needs a local pubspec.yaml before the CLI can start. Use dart run clean_architect ... only when developing this package from its own checkout or from another Dart project that already has a pubspec.yaml.
Commands #
clean_architect --version
clean_architect <command> --help
clean_architect create feature --help
clean_architect init
clean_architect doctor
clean_architect create architecture
clean_architect create base
clean_architect create auth
clean_architect create feature <name>
clean_architect create usecase <name> --feature <feature>
clean_architect create repository <feature>
clean_architect create remote-function <name> --feature <feature>
clean_architect create local-function <name> --feature <feature>
clean_architect create cached-function <name> --feature <feature>
Examples:
clean_architect init
clean_architect create architecture
clean_architect create feature orders
clean_architect create auth
clean_architect create usecase login --feature auth
clean_architect create repository auth
clean_architect create remote-function loadDetails --feature orders
clean_architect create local-function readDraft --feature orders
clean_architect create cached-function syncDetails --feature orders
clean_architect doctor
Useful flags:
clean_architect init --dry-run
clean_architect init --force
clean_architect create architecture --dry-run
clean_architect create auth --dry-run
clean_architect create auth --overwrite
clean_architect create auth --force
clean_architect create feature profile --skip-presentation
clean_architect create auth --state getx
clean_architect create auth --state bloc
clean_architect create auth --state provider
clean_architect create auth --state none
clean_architect create auth --network dio
clean_architect create auth --network abstract
clean_architect create auth --storage secure_storage
clean_architect create auth --storage shared_preferences
clean_architect create auth --storage hive
clean_architect create auth --storage objectbox
clean_architect create auth --storage abstract
clean_architect create auth --di injectable
clean_architect create auth --dependency-injection manual
clean_architect create feature orders --use-either-failure
clean_architect create feature orders --no-use-either-failure
clean_architect create architecture --flutter-create --platforms android,ios
clean_architect create auth --flutter-create --platforms android,ios,web
--overwrite and --force are required before conflicting generated files are replaced. Complete features and identical generated files are treated as idempotent no-ops.
Configuration File #
Run:
clean_architect init
This creates clean_architect.yaml:
clean_architect:
config_version: 1
structure: layered_packages # layered_packages or feature_first
state_management: getx # getx, bloc, provider, or none
network: dio # dio or abstract
local_storage: secure_storage # secure_storage, shared_preferences, hive, objectbox, or abstract
dependency_injection: manual # manual or injectable
use_asset_generator: true
use_either_failure: false
flutter:
create_presentation: false
platforms:
- android
- ios
models:
use_freezed: true
use_json_serializable: true
paths:
domain: domain/lib
data: data/lib/features
presentation: presentation/lib
di: di/lib
Configuration Reference #
| Key | Values | Default | What it controls |
|---|---|---|---|
config_version |
positive integer | 1 |
Configuration schema version used for future migrations. |
structure |
layered_packages, feature_first |
layered_packages |
How feature paths are resolved from the configured layer paths. |
state_management |
getx, bloc, provider, none |
getx |
Presentation controller/page style. |
network |
dio, abstract |
dio |
Remote data source style and generated data dependencies. |
local_storage |
secure_storage, shared_preferences, hive, objectbox, abstract |
secure_storage |
Local auth credential storage style and generated storage dependencies. |
dependency_injection |
manual, injectable |
manual |
Manual DI builder files or injectable/get_it setup files and annotations. |
use_asset_generator |
true, false |
true |
Whether presentation gets asset_generator_kit.yaml and the asset generator dependency. |
use_either_failure |
true, false |
false |
Whether generated repositories, repository implementations, and use cases return Future<Either<Failure, T>>. |
flutter.create_presentation |
true, false |
false |
Whether to run flutter create . automatically inside the generated presentation package. |
flutter.platforms |
list or comma-separated text | android, ios |
Platforms passed to flutter create . --platforms=.... |
models.use_freezed |
true, false |
true |
Whether entities/DTOs use Freezed. |
models.use_json_serializable |
true, false |
true |
Whether DTOs include JSON serialization parts/factories. |
paths.domain |
path | domain/lib |
Domain layer feature root. |
paths.data |
path | data/lib/features |
Data layer feature root. |
paths.presentation |
path | presentation/lib |
Presentation layer root. |
paths.di |
path | di/lib |
Dependency injection layer root. |
Use abstract when you want the source boundaries without a concrete local storage package.
CLI overrides are intentionally small and only affect the current command. They do not rewrite clean_architect.yaml.
Generated Project Shape #
The default architecture is a multi-package Flutter/Dart workspace shape:
my_app/
domain/
pubspec.yaml
lib/
features/
base_feature/
entities/
repositories/
usecases/
data/
pubspec.yaml
lib/
features/
base_feature/
remote/
models/
local/
models/
repositories/
di/
pubspec.yaml
lib/
presentation/
pubspec.yaml
analysis_options.yaml
asset_generator_kit.yaml
assets/
images/
icons/
lib/
main.dart
widgets/
pages/
utils/
controllers/
constants/
Every layer gets its own pubspec.yaml:
domainis a pure Dart package for entities, repository contracts, and use cases.datais a Dart/Flutter package for DTOs, remote data sources, local sources, mappers, and repository implementations.diis a Dart package that connectsdomainanddatadependencies.presentationis a runnable Flutter package withmain.dart, Flutter dependencies, and UI folders.
After generation, run Flutter setup inside presentation when you want a complete Flutter platform project:
cd presentation
flutter create .
flutter pub get
Or let clean_architect do the Flutter project bootstrap automatically:
clean_architect create architecture --flutter-create --platforms android,ios
The same behavior can be configured in clean_architect.yaml:
clean_architect:
flutter:
create_presentation: true
platforms:
- android
- ios
- web
When enabled, the CLI runs flutter create . --platforms=<platforms> from the generated presentation/ package root after writing the architecture files. --dry-run prints the command without executing it. --skip-presentation disables this step for the current command.
Run dart pub get in the other layer packages as needed.
Flutter Presentation Bootstrap #
By default, clean_architect creates the presentation package files but does not run Flutter tooling. This keeps generation fast and works even on machines without Flutter installed.
To generate Flutter platform folders automatically, use:
clean_architect create architecture --flutter-create --platforms android,ios
Supported platform names are the Flutter platform names: android, ios, web, macos, windows, and linux.
The YAML equivalent is:
clean_architect:
flutter:
create_presentation: true
platforms: android,ios
or:
clean_architect:
flutter:
create_presentation: true
platforms:
- android
- ios
This only runs for commands that generate presentation structure: create architecture, create base, create auth, and create feature <name>. It is skipped for operation commands, create usecase, create repository, and commands using --skip-presentation.
Structure Modes #
Default Layered Packages #
clean_architect:
config_version: 1
structure: layered_packages
paths:
domain: domain/lib
data: data/lib/features
presentation: presentation/lib
di: di/lib
clean_architect create auth creates:
domain/lib/features/auth/...
data/lib/features/auth/...
di/lib/auth_di.dart
presentation/lib/controllers/auth_controller.dart
presentation/lib/pages/login_page.dart
Custom Layer Paths #
You can point the layers to existing packages or app folders:
clean_architect:
config_version: 1
structure: layered_packages
paths:
domain: packages/domain/lib/modules
data: packages/data/lib/modules
presentation: apps/customer_app/lib
di: packages/di/lib
Then clean_architect create feature profile creates feature files under those configured roots.
Feature First #
feature_first is available for projects that still want feature-based grouping while keeping the same layer packages:
clean_architect:
config_version: 1
structure: feature_first
paths:
domain: domain/lib
data: data/lib/features
presentation: presentation/lib
di: di/lib
Feature-first resolution keeps the four layer packages while grouping each feature inside every layer:
domain/lib/features/<feature>/...
data/lib/features/<feature>/...
presentation/lib/features/<feature>/pages/...
presentation/lib/features/<feature>/controllers/...
di/lib/features/<feature>/...
create architecture #
clean_architect create architecture
Generates the layer packages and default folders only. It does not generate auth code.
Default placeholder feature name:
domain/lib/features/base_feature
data/lib/features/base_feature
Use this when you want the clean architecture project skeleton first, then add features later.
create feature <name> #
clean_architect create feature orders
Generates a generic feature module.
Domain:
domain/lib/features/orders/entities/orders_entity.dart
domain/lib/features/orders/repositories/orders_repository.dart
domain/lib/features/orders/usecases/get_orders_list_use_case.dart
Data:
data/lib/features/orders/remote/models/orders_dto.dart
data/lib/features/orders/remote/orders_remote_data_source.dart
data/lib/features/orders/local/models/orders_box.dart
data/lib/features/orders/local/orders_local_data_source.dart
data/lib/features/orders/mappers/orders_mapper.dart
data/lib/features/orders/repositories/orders_repository_impl.dart
Presentation, unless --skip-presentation is used:
presentation/lib/controllers/orders_controller.dart
presentation/lib/pages/orders_page.dart
The generic feature is intentionally minimal: entity, DTO, mapper, repository contract, repository implementation, list use case, local source, Retrofit remote data source, controller, and page. Its OrdersViewItem is a real widget declared in orders_page.dart, so a one-off UI element does not get its own file.
create auth #
clean_architect create auth
Generates a concrete auth starter feature.
Domain:
domain/lib/features/auth/entities/auth_token_entity.dart
domain/lib/features/auth/entities/auth_credentials_entity.dart
domain/lib/features/auth/repositories/auth_repository.dart
domain/lib/features/auth/usecases/login_use_case.dart
domain/lib/features/auth/usecases/logout_use_case.dart
domain/lib/features/auth/usecases/save_auth_credentials_use_case.dart
domain/lib/features/auth/usecases/get_auth_credentials_use_case.dart
domain/lib/features/auth/usecases/clear_auth_credentials_use_case.dart
Data:
data/lib/features/auth/remote/models/auth_token_dto.dart
data/lib/features/auth/remote/models/login_request_dto.dart
data/lib/features/auth/remote/auth_remote_data_source.dart
data/lib/features/auth/local/models/auth_box.dart
data/lib/features/auth/local/auth_local_data_source.dart
data/lib/features/auth/mappers/auth_token_mapper.dart
data/lib/features/auth/repositories/auth_repository_impl.dart
Presentation, unless --skip-presentation is used:
presentation/lib/controllers/auth_controller.dart
presentation/lib/pages/login_page.dart
Login form data is generated as LoginState in auth_controller.dart; it is
not emitted as a non-widget file under widgets/.
The generated remote remote data source uses Dio + Retrofit style:
@lazySingleton
@RestApi(baseUrl: '')
abstract class AuthRemoteDataSource {
@factoryMethod
factory AuthRemoteDataSource(@Named("auth_dio") Dio dio) = _AuthRemoteDataSource;
@POST('/authorization/token/')
Future<AuthTokenDto> login(@Body() Map<String, dynamic> body);
}
The generated auth controller uses GetIt.instance.get<LoginUseCase>(), and the GetX page registers the controller in initState with Get.put(AuthController()).
Operation Commands #
Operation commands add a new function to an existing feature. They generate the required entity/model/usecase support files and patch the existing source, repository, repository implementation, and controller files.
Remote Function #
clean_architect create remote-function loadDetails --feature orders
Aliases: remote-function, remote-method.
Adds:
domain/lib/features/orders/entities/load_details_entity.dart
domain/lib/features/orders/usecases/load_details_use_case.dart
data/lib/features/orders/remote/models/load_details_dto.dart
data/lib/features/orders/mappers/load_details_mapper.dart
Patches:
data/lib/features/orders/remote/orders_remote_data_source.dart
domain/lib/features/orders/repositories/orders_repository.dart
data/lib/features/orders/repositories/orders_repository_impl.dart
presentation/lib/controllers/orders_controller.dart
Local Function #
clean_architect create local-function readDraft --feature orders
Aliases: local-function, local-method.
Adds:
domain/lib/features/orders/entities/read_draft_entity.dart
domain/lib/features/orders/usecases/read_draft_use_case.dart
data/lib/features/orders/local/models/read_draft_box.dart
data/lib/features/orders/mappers/read_draft_mapper.dart
Patches the local source, repository contract, repository implementation, and controller.
Cached Function #
clean_architect create cached-function syncDetails --feature orders
Aliases: cached-function, cached-method.
Adds remote and local support together:
domain/lib/features/orders/entities/sync_details_entity.dart
domain/lib/features/orders/usecases/sync_details_use_case.dart
domain/lib/features/orders/usecases/stream_details_use_case.dart
data/lib/features/orders/remote/models/sync_details_dto.dart
data/lib/features/orders/local/models/sync_details_box.dart
data/lib/features/orders/mappers/sync_details_mapper.dart
data/lib/features/orders/mappers/sync_details_box_mapper.dart
Patches the remote source with syncDetails(), the local source with
streamDetails(), and uses those same names in the repository, use cases,
repository implementation, and controller. The generated use cases are
SyncDetailsUseCase and StreamDetailsUseCase.
Dependency Injection Modes #
Manual #
dependency_injection: manual
Manual mode generates simple DI builder files, for example:
di/lib/auth_di.dart
di/lib/orders_di.dart
Use this when you want explicit constructors and simple dependency wiring you can edit by hand.
Injectable #
dependency_injection: injectable
Injectable mode adds injectable/get_it dependencies and generates injector entry files:
domain/lib/injector.dart
data/lib/injector.dart
di/lib/di.dart
Generated classes receive injectable annotations where supported. After generation, run build runner in the generated packages that contain injectable/freezed/json_serializable code.
cd domain
dart run build_runner build
cd ../data
dart run build_runner build
Either / Failure Return Type #
use_either_failure: true
When enabled, generated repository contracts, repository implementations, and use cases use:
Future<Either<Failure, T>>
instead of:
Future<T>
The generator also creates domain/lib/failures/failure.dart where needed and adds dartz to generated layer pubspecs. You can override the value for one command with --use-either-failure or --no-use-either-failure.
Model Modes #
Freezed + JSON Serializable #
models:
use_freezed: true
use_json_serializable: true
Entities and DTOs use Freezed. DTOs also include JSON serialization parts when JSON serialization is enabled.
Typical follow-up command in generated packages:
dart run build_runner build
Plain Dart Fallback #
models:
use_freezed: false
use_json_serializable: false
Entities and DTOs are generated as simple Dart classes.
State Management #
GetX #
state_management: getx
Presentation controllers extend GetxController, pages use Get.put(...), Get.find(...), and reactive values where needed.
Bloc #
state_management: bloc
Presentation controllers use flutter_bloc with event/state classes, and pages use BlocProvider/BlocBuilder.
Provider #
state_management: provider
Presentation controllers extend ChangeNotifier, and pages use ChangeNotifierProvider/Consumer.
None #
state_management: none
Presentation files are generated without state management package wiring. This is useful when you want to connect another state system manually.
Network #
Dio #
network: dio
Generated remote services use Dio + Retrofit imports and annotations. The data package receives the relevant dependencies.
Abstract #
network: abstract
Use this when you want the generated repositories and source boundaries but plan to implement networking yourself.
Local Storage #
Secure Storage #
local_storage: secure_storage
Auth local source uses flutter_secure_storage for credential persistence.
Hive #
local_storage: hive
The data package uses the maintained Hive CE runtime and generator packages. Local sources can initialize their boxes directly. Injectable projects get a generated module that initializes Hive, registers each adapter with a stable type ID, opens each box, and provides the box to its local data source:
data/lib/data_module.dart
Adding another Hive feature or local/cached operation updates the same module with the additional adapter and box provider.
Generated local models keep an integer id for the Hive/ObjectBox database key
and a String remoteId for the API identity. DTO id values map to
Entity.remoteId and Box.remoteId, so remote identifiers never overwrite local
database keys.
ObjectBox #
local_storage: objectbox
The data package gets ObjectBox dependencies, local sources can initialize their box from a Store, and injectable projects get a generated module:
data/lib/data_module.dart
Run build runner in the data package after adding ObjectBox entities so objectbox.g.dart can be generated.
Abstract #
local_storage: abstract
Auth local source contains TODO methods so you can wire a custom persistence mechanism yourself.
Dependency Compatibility #
Version 0.3.0 emits only the dependencies required by the selected configuration. Runtime packages use their current stable releases. Builder packages use the newest mutually compatible stable set verified against generated Flutter projects:
build_runner: ^2.15.1
freezed: ^3.2.5
injectable_generator: ^3.0.2
hive_ce_generator: 1.11.1
Some newer individual builder releases target incompatible analyzer versions. These constraints avoid prerelease transitive dependencies while allowing Freezed, Retrofit, Injectable, and Hive CE generation to run together.
Presentation Package #
The generated presentation package includes:
presentation/lib/main.dart
presentation/lib/widgets/
presentation/lib/pages/
presentation/lib/utils/
presentation/lib/controllers/
presentation/lib/constants/
presentation/assets/images/
presentation/assets/icons/
When use_asset_generator: true, it also includes:
presentation/asset_generator_kit.yaml
and adds assetgeneratorkit to presentation/pubspec.yaml.
Safety #
Every generation command builds a complete in-memory plan first. Names, configuration, required features, duplicate outputs, and filesystem conflicts are validated before the first file is written. If any conflict is found, the command aborts without partially generating the remaining files.
Identical files are left untouched, and rerunning a completed feature or operation is an idempotent no-op. Operation commands require the feature to already exist:
clean_architect create feature orders
clean_architect create cached-function syncCatalog --feature orders
By default, conflicting existing files are not overwritten.
Preview generated files:
clean_architect create auth --dry-run
Overwrite intentionally:
clean_architect create auth --overwrite
or:
clean_architect create auth --force
Doctor #
clean_architect doctor
doctor validates the complete generated project:
- Every configured package root and layer path exists and is structurally valid.
- Each layer has a readable
pubspec.yamlwhose package name matches its root. - Required dependencies and dev dependencies are present with compatible version constraints.
- Local path dependencies resolve to the configured domain, data, and DI packages.
- The active Dart and Flutter versions satisfy the generated package constraints.
build_runneris declared and resolved in every package that needs it.- Every referenced generated
.g.dartfile exists.
A healthy project exits with code 0. Failed validation exits with code 1, so
clean_architect doctor can be used directly in CI.
Integration Test Matrix #
The integration suite generates complete projects in temporary directories, then runs dependency resolution, code generation, analysis in every layer, and the project doctor.
| Scenario | Coverage |
|---|---|
default_getx_manual_dio_secure |
Layered packages, GetX, manual DI, Dio, secure storage |
bloc_injectable_hive_feature_first |
Feature first, Bloc, Injectable, Hive CE |
provider_injectable_objectbox |
Layered packages, Provider, Injectable, ObjectBox |
none_abstract_plain_feature_first |
Feature first, no state package, abstract sources, plain Dart models |
either_enabled |
Layered packages with Either<Failure, T> returns |
Normal dart test runs skip these expensive cases. Run the full matrix with:
CLEAN_ARCHITECT_INTEGRATION=all dart test test/integration/generated_project_matrix_test.dart
Run one scenario while developing:
CLEAN_ARCHITECT_INTEGRATION=provider_injectable_objectbox dart test test/integration/generated_project_matrix_test.dart
Keep generated projects after success for manual inspection:
CLEAN_ARCHITECT_INTEGRATION=all CLEAN_ARCHITECT_KEEP_INTEGRATION_PROJECTS=true dart test test/integration/generated_project_matrix_test.dart
Every scenario creates the architecture, auth, an orders feature, and remote,
local, and cached operations, then requires clean_architect doctor to pass. GitHub Actions runs each scenario as a separate
matrix job.
0.9 Release Candidate Contract #
Version 0.9.0 freezes the interface planned for 1.0. Existing commands,
options, configuration keys, generated package roots, and exported Dart APIs
will not be removed or renamed before 1.0.
Frozen CLI surface #
| Command | Status |
|---|---|
init |
Supported |
doctor |
Supported |
create architecture |
Supported |
create auth |
Supported |
create feature <name> |
Supported |
create usecase <name> --feature <feature> |
Supported |
create repository <feature> |
Supported |
create remote-function <name> --feature <feature> |
Supported |
create local-function <name> --feature <feature> |
Supported |
create cached-function <name> --feature <feature> |
Supported |
create base, remote-method, local-method, cached-method, and --di
remain supported compatibility aliases. Canonical names are used in new
documentation and examples.
Frozen public Dart API #
Import supported APIs from:
import 'package:clean_architect/clean_architect.dart';
The 1.0 public surface includes the configuration enums and value objects,
CleanArchitectGenerator, GeneratedFile, OperationKind, PathResolver,
FeaturePaths, ProjectDoctor, its diagnostic/report types,
currentConfigVersion, and packageVersion. Files below lib/src are
implementation details and are not covered by compatibility guarantees.
Backward compatibility and upgrades #
- Patch releases fix defects without intentionally changing generated output.
- Minor releases may add commands, options, configuration keys, templates, or public APIs. Existing valid configuration remains valid.
- A command, key, alias, or public API must be deprecated for at least one minor release before removal.
- Breaking changes are reserved for a new major version after 1.0.
- Generated source is owned by the consuming project. Upgrading the CLI never
rewrites existing files unless the user explicitly passes
--forceor--overwrite. config_versioncontrols schema migrations independently from the package version. New optional keys retain defaults; incompatible schema changes incrementconfig_versionand include migration instructions.- Golden snapshots protect representative generated output. Intentional template changes must update the corresponding goldens and changelog.
Before upgrading, commit the consuming project, update clean_architect, run
clean_architect doctor, preview generation with --dry-run, and review any
intentional snapshot-level output changes described in the changelog.
Runnable Example #
example/ is a checked-in generated project with domain, data,
di, and a Flutter presentation package. It can be launched with:
cd example/presentation
flutter pub get
flutter run -d chrome
CI resolves and analyzes this example and builds its web target on every change.
Publishing / Development Notes #
When working on this package locally:
dart format lib test bin
dart analyze
dart test
UPDATE_GOLDENS=true dart test test/golden_generation_test.dart
dart pub publish --dry-run
Run dart pub publish --dry-run before publishing to verify pub.dev metadata and package contents.