Skip to content

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 fluzer in pubspec.yaml, so after global activate you can call it directly with fluzer.


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:

  1. Validate the feature name (must be snake_case, starting with a lowercase letter, e.g. user_profile).
  2. Check whether lib/features/<name>/ already exists.
  3. Render the feature brick (only passing the brick-declared name + package_name variables; class-name casing is handled by Mustache filters inside the brick).
  4. Write the module into lib/core/di/injection_base.dart's registerFeatureModules() via FeatureRegistration (built on CodeMod).
  5. Run build_runner as 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:

  1. Validate the project name (starts with a lowercase letter; only lowercase letters, digits, underscores).
  2. Render the project brick into the current directory (only the name variable), generating the ./<name> project directory directly.
  3. Run flutter create . --org --project-name.
  4. Remove the default test/widget_test.dart generated by flutter create (the template ships its own home_page_test.dart).
  5. Run flutter pub get.
  6. Run flutter gen-l10n.
  7. Run build_runner as 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 create no longer accepts --desc (project description). Edit pubspec.yaml manually 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_featureflutter run.


5. version — Show version & check for updates

fluzer version
  • Prints the CLI version (from the cliVersion constant, which must stay in sync with pubspec.yaml's version).
  • Queries pub.dev for a new version:
  • Queries the package name fluzer by 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 fluzer to 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 under fluzer_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 keeps version_check.json (this is the update-check cache for the version command, not part of the template cache).
  • Running fluzer cache without a subcommand prints help (listing list / clean usage).

To force re-pull a specific template version, run fluzer cache clean first, then create / new.


7. Template Source Resolution

Both new and create rely on resolveBrickLoader() to decide where to load the Mason brick. Resolution priority:

  1. FLUZER_BRICKS_DIR non-empty → LocalBrickLoader (local dev / debugging, points to the bricks/ root).
  2. FLUZER_TEMPLATE_ZIP_URL non-empty → force RemoteBrickLoader of that URL (testing / debugging).
  3. Otherwise go to the remote registry: pull template_registry.json from templateRegistryUrl, and among records where minCliVersion <= cliVersion pick the zip URL with the largest version; if the pull fails (or direct connection times out at 5s) fall back to defaultTemplateZipUrl.
# 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 templateRegistryUrl and defaultTemplateZipUrl placeholders (https://github.com/<owner>/<repo>/...) in template_config.dart with real addresses, and keep cliVersion in sync with pubspec.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:

  • version is a valid string and >= 1.0.0.
  • template_name must be exactly flutter_zero.
  • The root contains pubspec.yaml (reads name as package_name), lib/, and lib/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 (default checkForUpdate, 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

  • new reports "flutter_zero_config.yaml not found": cd into the template project root (the one containing that file) before running.
  • create reports "directory already exists": pick another project name; the existing directory will not be deleted.
  • version keeps 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_URL to specify a specific Release's zip link.
  • cache list is 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 clean to clear the cache; the next create / new will re-download.