fluzer — Scaffolding CLI¶
fluzer is the command-line tool for the Flutter Zero template, providing four groups of commands:
create: generate a brand-new Flutter project from the template (with complete core infrastructure, example modules, and configuration).new: add a feature-module skeleton in an existing template project, and auto-register it in DI.cache: view or clear locally downloaded template caches (cache list/cache clean).version: show the CLI's own version and check for updates.
The template and CLI are decoupled: the CLI renders code through Mason bricks (under
flutter_zero_template/bricks/); the brick's variable contract and generated structure evolve independently, so the template can release frequently without upgrading the CLI.
1. Quick Start¶
Dev mode (inside the flutter_zero_cli directory)¶
dart run bin/fluzer.dart new user
dart run bin/fluzer.dart create my_app
dart run bin/fluzer.dart cache list
dart run bin/fluzer.dart version
Global install¶
dart pub global activate fluzer
fluzer new user # add a feature module
fluzer create my_app # create a new project
fluzer cache list # list cached template versions
fluzer version # show version + check for updates
The CLI registers the executable name
fluzerinpubspec.yaml, so afterglobal activateyou can call it directly withfluzer.
2. Command Overview¶
| Command | Purpose | Common options |
|---|---|---|
fluzer new <feature_name> |
Add a feature module in the current template project and register DI | --build-runner / --no-build-runner |
fluzer create <project_name> |
Create a brand-new Flutter project from the template | --org, --build-runner / --no-build-runner |
fluzer cache list |
List downloaded cached template versions | — |
fluzer cache clean |
Clear all cached template versions | — |
fluzer version |
Print the CLI version and check pub.dev for updates | — |
3. new — Add a feature module¶
Must be run in the root directory of a Flutter Zero template project (the one containing flutter_zero_config.yaml).
Execution flow:
- Validate the feature name (must be
snake_case, starting with a lowercase letter, e.g.user_profile). - Check whether
lib/features/<name>/already exists. - Render the
featurebrick (only passing the brick-declaredname+package_namevariables; class-name casing is handled by Mustache filters inside the brick). - Write the module into
lib/core/di/injection_base.dart'sregisterFeatureModules()viaFeatureRegistration(built onCodeMod). - Run
build_runneras needed.
fluzer new user
# options
# --build-runner run build_runner after generation (enabled by default)
# --no-build-runner skip build_runner (you can run dart run build_runner build later)
The generated module contains
data/,domain/,presentation/skeletons and auto-generates<name>_module.dart. For how to write business logic after adding a module, see Write Your First Feature.
4. create — Create a new project¶
Execution steps:
- Validate the project name (starts with a lowercase letter; only lowercase letters, digits, underscores).
- Render the
projectbrick into the current directory (only thenamevariable), generating the./<name>project directory directly. - Run
flutter create . --org --project-name. - Remove the default
test/widget_test.dartgenerated byflutter create(the template ships its ownhome_page_test.dart). - Run
flutter pub get. - Run
flutter gen-l10n. - Run
build_runneras needed.
fluzer create my_app
# options
# --org <org> organization identifier (default com.example, affects bundle ID)
# --build-runner run build_runner after generation (enabled by default)
# --no-build-runner skip build_runner
Note: the current
createno longer accepts--desc(project description). Editpubspec.yamlmanually after generation for the project description.
If the target directory already exists, it reports an error and cleans up the half-made directory it created itself (it will not delete the content of your existing same-named directory — it only cleans up if the directory was created by this command; if the directory already existed, create will simply ask you to pick another name and delete nothing).
After a successful creation it suggests next steps: cd my_app → (optional) fluzer new my_feature → flutter run.
5. version — Show version & check for updates¶
- Prints the CLI version (from the
cliVersionconstant, which must stay in sync withpubspec.yaml'sversion). - Queries pub.dev for a new version:
- Queries the package name
fluzerby default; if unpublished, pub.dev returns 404, and it silently degrades to "cannot check for updates", not affecting the main flow. - Results are cached per package name for 24 hours (unavailable results cached for 10 minutes) to avoid hitting the API on every startup.
- Network exceptions / rate limits also degrade silently.
- If a new version is found, it prompts: run
dart pub global activate fluzerto upgrade.
6. cache — Manage template cache¶
fluzer caches the remotely pulled template zip under fluzer_cache/ in the system temp directory (directory name template_<version> or the fallback fluzer_<hash>). The cache command is for viewing and cleaning:
fluzer cache list # list all cached template versions
fluzer cache clean # clear all cached template versions
cache list: prints all cached version directories underfluzer_cache/(sorted by name); if the directory doesn't exist or is empty, it prints a hint instead of erroring.cache clean: deletes all cached version subdirectories, but keepsversion_check.json(this is the update-check cache for theversioncommand, not part of the template cache).- Running
fluzer cachewithout a subcommand prints help (listinglist/cleanusage).
To force re-pull a specific template version, run
fluzer cache cleanfirst, thencreate/new.
7. Template Source Resolution¶
Both new and create rely on resolveBrickLoader() to decide where to load the Mason brick. Resolution priority:
FLUZER_BRICKS_DIRnon-empty →LocalBrickLoader(local dev / debugging, points to thebricks/root).FLUZER_TEMPLATE_ZIP_URLnon-empty → forceRemoteBrickLoaderof that URL (testing / debugging).- Otherwise go to the remote registry: pull
template_registry.jsonfromtemplateRegistryUrl, and among records whereminCliVersion <= cliVersionpick the zip URL with the largestversion; if the pull fails (or direct connection times out at 5s) fall back todefaultTemplateZipUrl.
# Local debug: read the local bricks directory directly
export FLUZER_BRICKS_DIR=../flutter_zero_template/bricks
fluzer new user
# Debug: force a specific remote zip
export FLUZER_TEMPLATE_ZIP_URL=https://github.com/<owner>/<repo>/releases/download/1.0.0/bricks.zip
fluzer create demo
RemoteBrickLoader caches the zip to the temp directory after download: when the registry version number is available it names the cache directory template_<version>; on env-var override / fallback it degrades to a URL-hash name; different versions don't overwrite each other, and path validation is done on extraction (Zip Slip protection).
Required before release: replace the
templateRegistryUrlanddefaultTemplateZipUrlplaceholders (https://github.com/<owner>/<repo>/...) intemplate_config.dartwith real addresses, and keepcliVersionin sync withpubspec.yaml. The registry uses a "compatibility bucket" structure; see CLI Versioning.
8. Config File flutter_zero_config.yaml¶
The new command depends on flutter_zero_config.yaml in the template project root. ProjectConfig.load() searches upward for this file and validates the project structure.
version: 1.0.0 # template version, must be >= minimum supported version (1.0.0)
template_name: flutter_zero
Validation:
versionis a valid string and>= 1.0.0.template_namemust be exactlyflutter_zero.- The root contains
pubspec.yaml(readsnameaspackage_name),lib/, andlib/core/di/injection_base.dart.
Any unmet item throws CliException and aborts, prompting you to run it in the correct template project root.
9. Directory Structure¶
fluzer/
├── bin/
│ └── fluzer.dart # Entry point
├── lib/
│ └── src/
│ ├── fluzer.dart # CLI root controller (CommandRunner assembly + root exception fallback + UsageException help print)
│ ├── commands/
│ │ ├── create_command.dart # create command (7-step flow + injection executor)
│ │ ├── new_command.dart # new command (render + register DI)
│ │ ├── cache_command.dart # cache command (list / clean cache)
│ │ └── version_command.dart # version command (injectable update check)
│ ├── codemod/
│ │ ├── code_mod.dart # AST editing core (CodeMod: sorted addImport / idempotent insertAtMethodEnd)
│ │ ├── codemod_file_editor.dart # generic file-edit wrapper
│ │ ├── feature_registration.dart # DI registration wrapper (depends on CodeMod)
│ │ ├── insert_at_method_end_transform.dart # insert-at-method-end transform
│ │ └── ordered_import_transform.dart # ordered import insert transform
│ ├── config/
│ │ └── project_config.dart # project config loading + CliException
│ ├── template/
│ │ ├── brick_loader.dart # BrickLoader abstraction + Local / Remote loaders
│ │ ├── brick_renderer.dart # Mason render wrapper (BrickRenderer.generate)
│ │ ├── feature_generator.dart # feature module generator (render + call FeatureRegistration)
│ │ ├── template_source.dart # template source resolution: pick BrickLoader
│ │ ├── template_config.dart # centralized config: registry/zip URL, mirror prefixes, cache dir name
│ │ └── semantic_version.dart # SemVer parse & compare (unified version comparison logic)
│ ├── http/
│ │ └── http_client.dart # FluzerHttpClient: unified Dio instance + mirror-fallback retry
│ ├── process/
│ │ └── process_runner.dart # ProcessRunner: unified process execution (flutter / dart)
│ ├── util/
│ │ ├── string_case.dart # naming conversion utilities
│ │ └── regular_utils.dart # general utilities (e.g. extract version from URL)
│ └── version/
│ └── version_check.dart # pub.dev update check (available results cached 24h, unavailable 10min)
├── test/
│ ├── fluzer_test.dart # command-layer + version-check unit tests
│ └── brick_test.dart # brick render smoke test
└── pubspec.yaml
10. Tech Stack¶
| Category | Solution |
|---|---|
| Argument parsing / CLI framework | args (CommandRunner + Command) |
| Log output | mason_logger (colored console) |
| Template rendering | mason (brick + Mustache filters) |
| Template download / unzip | dio + archive |
| AST code modification | analyzer + codemod_recipe (wrapped as CodeMod) |
| YAML parsing | yaml |
| Path operations | path |
11. Development & Testing¶
Local debugging¶
Inside the flutter_zero_cli directory use dart run bin/fluzer.dart ..., and point to a local or specified remote template via the env vars FLUZER_BRICKS_DIR / FLUZER_TEMPLATE_ZIP_URL, avoiding the registry on every run.
Inject executors for testability¶
Both commands and the version check inject external implementations via typedef, for easy unit testing:
CreateCommand:CreateFlutterCreateRunner/CreateFlutterPubGetRunner/CreateFlutterGenL10nRunner/CreateBuildRunnerRunner+BrickLoader.NewCommand:BuildRunnerRunner+BrickLoader.VersionCommand:CheckForUpdate(defaultcheckForUpdate, queries pub.dev).
Run tests¶
dart analyze # 0 issues
dart test # includes command-layer (create/new/version) and network-fallback paths
Coverage highlights: project-name / feature-name validation, target directory already exists, the full generation flow, cleanup on flutter create failure, the version-check's three branches (update available / up to date / unavailable), and cache list / clean.
12. Common Troubleshooting¶
newreports "flutter_zero_config.yaml not found":cdinto the template project root (the one containing that file) before running.createreports "directory already exists": pick another project name; the existing directory will not be deleted.versionkeeps saying "cannot check for updates": the package is not yet published to pub.dev, or the network is restricted — this is a normal degradation and does not affect other commands.- Template pull is slow / want to pin a version: use
FLUZER_TEMPLATE_ZIP_URLto specify a specific Release's zip link. cache listis empty: you haven't created a project or pulled a remote template yet; an empty cache directory is normal.- Want to force-refresh the template: first
fluzer cache cleanto clear the cache; the nextcreate/newwill re-download.