boltforge
A CLI that scaffolds production-ready Flutter projects — Cubit + Repository + GetIt architecture, a wired authentication flow, and a feature generator that wires itself into dependency injection automatically — so every project your team starts looks the same on day one, and every feature you add afterward follows the same pattern without manual wiring.
What is boltforge?
Two commands. boltforge create turns an empty directory into a working
Flutter app with a real architecture already in place — networking,
error handling, DI, routing, and a complete login flow. boltforge generate adds a new feature (cubit, repository, model, screen) to that
app later, on demand, wired into the same DI container automatically.
It is deliberately backend-agnostic: nothing it generates assumes
Node.js, Firebase, Laravel, or any specific backend — you fill in one
constant (ApiConstants.baseUrl) and it works with any REST API.
Features
boltforge create <name>— Flutter project scaffolding with a Cubit + Repository + GetIt architecture, a Dio client with a Token → Retry → ErrorMapper interceptor chain, and a wired authentication flow:--auth=password(login/register/forgot-password) or--auth=otp(email/phone verification, account creation only for new identifiers).boltforge generate <name>— adds a feature (lib/features/<name>/{cubit,repository,model,presentation}) to an existing project and registers its repository intolib/core/di/service_locator.dartautomatically — no manual DI edit, no duplicate registrations even if you run it twice.- Post-generation validation — every
generaterun checks for missing files, leaked template files, unresolved placeholders, and missing/duplicate DI registrations, and reports real errors with a non-zero exit code instead of a silent false "success". boltforge doctor— confirms Dart and Flutter are set up correctly.- Safe by default — no file is ever silently overwritten; conflicting generation always asks to cancel, overwrite, or merge.
- A 14-page documentation site (in Arabic; code and commands stay in English) and an interactive project dashboard.
Install
dart pub global activate boltforge
Usage
# Create a new project (interactive auth prompt if --auth is omitted)
boltforge create my_app
boltforge create my_app --auth=password
boltforge create my_app --auth=otp
# Generate a feature inside an existing boltforge project
cd my_app
boltforge generate offer
# Check your environment
boltforge doctor
A complete example
$ boltforge create shop_app --auth=password
boltforge — creating "shop_app"
→ Creating Flutter project "shop_app"
✔ Flutter project scaffolded
→ Adding required dependencies
✔ Dependencies added to pubspec.yaml
→ Applying base architecture
✔ Base architecture applied (core/ layer)
→ Configuring authentication (password)
✔ Authentication flow generated (password)
→ Installing dependencies
✔ Dependencies installed
→ Validating generated code (flutter analyze)
✔ No analysis issues found
→ Running tests
✔ Tests passed
Project created successfully!
cd shop_app && flutter run
$ cd shop_app && boltforge generate offer
boltforge — generating feature "offer"
✔ Wrote lib/features/offer/model/offer_model.dart
✔ Wrote lib/features/offer/repository/offer_repository.dart
✔ Wrote lib/features/offer/cubit/offer_state.dart
✔ Wrote lib/features/offer/cubit/offer_cubit.dart
✔ Wrote lib/features/offer/presentation/screen/offer_screen.dart
✔ Wrote lib/features/offer/presentation/widgets/offer_list_item.dart
✔ DI: registered the repository in lib/core/di/service_locator.dart automatically.
Feature "offer" generated.
See example/ for the full walkthrough, including
what to do next (pointing the project at a real backend).
What create does
- Validates the project name.
- Runs
flutter create. - Adds
flutter_bloc,bloc,dio,get_it,shared_preferencesviaflutter pub add. - Applies the base architecture (
lib/core/...): Dio client with a Token → Retry → ErrorMapper interceptor chain, anApiResult<T>/ApiErrorcontract, aGetItservice locator, a lightweightNavigator-based router, shared loading/error/empty widgets, and a couple of reusable form widgets. - Applies your chosen authentication flow —
--auth=password(login, register, forgot password) or--auth=otp(email/phone verification, with account creation only when the identifier is new). - Runs
flutter pub get,flutter analyze, andflutter test, and stops and reports honestly if any step fails — it will not tell you the project was created successfully ifflutter analyzefound problems.
What generate does
Inside an existing project, boltforge generate <name> writes
lib/features/<name>/{cubit,repository,model,presentation/{screen,widgets}}
with real, compiling starter code (not empty files), following the same
Cubit + Repository conventions as the rest of the app. It then:
- Automatically registers the new repository in
lib/core/di/service_locator.dart— adding the import and asl.registerLazySingleton<XRepository>(() => XRepository());line, with no duplicate imports or registrations even if you run it again. This works by inserting before two stable marker comments (// boltforge:imports/// boltforge:registrations) baked into the generatedservice_locator.dart— if those markers are missing (e.g. you hand-rewrote the file), boltforge leaves it untouched and tells you to register the repository yourself, rather than guessing. - Validates the result: checks every expected file was written, that
no
.tmplsuffix leaked onto disk, that no{{placeholder}}was left unresolved, and that the DI registration exists exactly once (not zero, not more than one). Problems are printed as errors (exit code1) or warnings (exit code0, but flagged) — never silently swallowed.
If the feature already exists, you're prompted to cancel, overwrite, or
merge (write only the files that don't already exist); DI registration is
similarly idempotent, so re-running generate never produces a duplicate
entry.
Backend-agnostic by design
Nothing in the generated project assumes a specific backend. Set
ApiConstants.baseUrl and the endpoint paths in
lib/core/constants/api_constants.dart to point at your Node.js,
Firebase, Laravel, Django, or any REST backend — the ApiResult<T> /
ApiError contract and the Dio interceptor chain don't change.
Architecture of this repository
bin/boltforge.dart Entry point
lib/
boltforge.dart Tiny public barrel — also the resolution
target PackagePaths uses to find the
package root reliably (see below)
src/
cli_runner.dart CommandRunner wiring
commands/ create / generate / doctor
generators/ ProjectGenerator, FeatureGenerator (orchestration)
core/package_paths.dart Resolves templates/ correctly whether run
from source, a cached pub-global snapshot,
or an installed pub.flutter-io.cn package
services/ ProcessService, FileService, ProjectDetector,
DiRegistrar, FeatureValidator
template_engine/ {{variable}} + {{#if}} rendering
utils/ NameCase, Logger
validators/ NameValidator
templates/
base/ Core architecture applied to every project
(service_locator.dart.tmpl includes the DI
marker comments FeatureGenerator relies on)
auth/{password,otp}/ Authentication flow variants
feature/{{featureName}}/ Feature generator template (generates into
lib/features/<name>/, same convention as
the auth flow)
docs/ 14-page Arabic documentation site
project-dashboard.html Interactive project control center
Template resolution (read this if you're modifying templates/)
boltforge create/generate need to find the bundled templates/
directory at runtime — a non-Dart resource outside lib/. This is
not as simple as "look next to the running script": when boltforge is
activated globally (dart pub global activate), Dart caches a compiled
snapshot one directory level deeper than bin/boltforge.dart
(.dart_tool/pub/bin/boltforge/boltforge.dart-<sdk>.snapshot), which
broke exactly that assumption in an earlier version — boltforge create
would fail with a PathNotFoundException the moment it was run from
outside the repository, i.e. the way real users actually run it. This has
been fixed and confirmed working with a real boltforge create run
outside the repository (see CHANGELOG.md).
lib/src/core/package_paths.dart resolves templates/ via
Isolate.resolvePackageUri('package:boltforge/boltforge.dart') — the
same mechanism Dart itself uses to make every import 'package:boltforge/...'
in this program work at all, so it's correct regardless of how the
program was invoked. ProjectGenerator/FeatureGenerator require
templatesRoot as an explicit constructor parameter (no internal
default); CreateCommand/GenerateCommand resolve it once, async,
before constructing the generator, and fail clearly (exit code 70)
rather than surface a raw filesystem exception if it can't be found.
Tests pass a known directory directly — see test/package_paths_test.dart
for a regression test proving the resolver actually finds the real
templates/ tree.
Testing
dart pub get
dart format .
dart analyze
dart test
The test suite verifies real generated files, directory structure, and process behavior — not just "did it throw". CI runs the same three commands on both Ubuntu and Windows.
Documentation
Full documentation (in Arabic; code/commands/filenames stay in English)
lives under docs/ — start at docs/index.html. project-dashboard.html
is a live, localStorage-backed project control center with phase
checklists, testing status, and release/GitHub/pub.flutter-io.cn checklists.
Contributing
See CONTRIBUTING.md. Please also read the
Code of Conduct. Security issues should be reported
per SECURITY.md, not as a public issue.
License
MIT — see LICENSE.