Skip to main content

ag_flow_cli 0.1.0 copy "ag_flow_cli: ^0.1.0" to clipboard
ag_flow_cli: ^0.1.0 copied to clipboard

The `ag` CLI for ag_flow — one command generates a fully-wired feature module (page, controller, repo, service, binding, component) and wires its route, argument, and navigation method automatically.

Changelog #

Unreleased #

  • ag init now scaffolds the architecture standard, not just code. The goal is that a developer or a coding agent picking up a project already knows the rules, instead of being told them again every session.

    • AGENTS.md at the project root — the layer contract, the folder structure, the generator commands to run instead of writing files, and an explicit "you must not" list. AGENTS.md is the cross-tool convention coding agents read automatically; CLAUDE.md is a pointer to it rather than a second copy to drift.
    • .github/workflows/ag.yaml — runs ag analyze, formatting, static analysis and tests on every push. Instructions are advisory; this is the gate, and it is free on public repositories.
    • .githooks/pre-commit (opt-in via git config core.hooksPath .githooks) — the same check locally, so a violation is caught before CI. Written with the executable bit, which git silently requires.
    • All four are skipExisting like every other generated file, so re-running ag init never overwrites an edited standard.
    • The hook resolves the CLI through the project's own ag_flow_cli dev_dependency before anything on PATH. A global ag may be a different version — or, since the name is short, a different tool entirely; found the hard way when a globally-activated unrelated ag shadowed it and its missing analyze command was reported as an architecture violation.
  • ag --version / ag -v. Works with no sub-command, as every CLI should — and as any script probing for the tool will try first.

  • Typed models: ag g m product --from-json=sample.json. Paste a real API response and the generated module compiles against your data — the model class is generated and its type threaded through the Service, Repo, Controller, Page and item component. Previously a fresh module handed back 17 dynamic placeholders across 5 files to replace by hand, which is the opposite of what a generator is for.

    • --model=Product alone names the type without inferring fields.
    • Inference handles nested objects (as nested classes), lists of objects and of scalars, snake_case/camelCase/kebab-case keys (keeping the original JSON key for encoding), Dart keyword collisions, and reads doubles through num so a whole-number payload doesn't throw. A null sample value becomes Object? rather than a guess.
    • A list response and a single-key envelope ({"data": [...]}) are both understood, so a real response body works unedited. Only known envelope keys are looked through — {"rating": {...}} is a one-field model, not an envelope.
    • A detail module reuses its root's model (product/details gets Product), and its idOf is generated with the path-parameter conversion already written (int.parse(argument.id)).
    • Generated models are plain data classes: no build_runner, no annotations, no part file. The file is skipped if it already exists, so edits survive regeneration.
    • Entirely additive: without --model/--from-json the output is unchanged.
  • The CLI's own description no longer says "Flutter/GetX" — the GetX dependency was removed some releases ago.

  • Generated Services are now pure declaration — no stub bodies at all. A generated collection Service is with AgCrudService<..>, AgPagedService<..> plus five declared values (two endpoints, a pageStrategy, an envelope, fromJson/toJson); a detail Service is the same minus paging, plus a single idOf hook naming which field of the navigation argument identifies the resource. There is no request, decoding, or hasMore code left to write or delete.

    • Consequently --methods= now shapes the Repo and Controller passthroughs, not the Service. Its original purpose was avoiding stub bodies a developer had to delete; the mixins provide those methods for free, as zero lines of code, so there is nothing left to opt out of at the Service layer.
    • The detail Repo calls updateByArgument/deleteByArgument — distinct names because AgCrudService.update(ID, T) already occupies update.
  • Generated code no longer references GetX. Following ag_flow's removal of the get dependency, every template and aggregator updater now emits AG's own primitives: AgBinding + AgLocator.find in bindings, AgRoute/AgTransition in app_pages.dart, AgNavigator.toNamed in route_management.dart, and AgApp in the main.dart wiring ag init prints. ag analyze's hard-coded-route check matches AgNavigator.toNamed('/literal'), and its route-wiring check matches AgRoute entries. Golden fixtures regenerated from real tool output, as always.

  • The Controller's generated update stub is named update again, not updateItem. That workaround existed solely because GetxController declared its own update([List&lt;Object&gt;? ids, bool condition]) and a same-named override was a real invalid_override error; AgBaseController extends ChangeNotifier now, which declares no update. Removing it also resolves a naming hazard it had introduced — updateItem sat one character from AgPaginationMixin.updateItems — and restores symmetry, since Service and Repo always used update.

  • Reverted the success-content split from the "pure wiring" page change below: _list.dart (collection) and _view.dart (detail) are gone. Neither was a genuinely reusable widget — each was just the one page's own body, wrapped in an extra class for no reason, unlike the app bar/ loading/error/empty slots (real, independently-overridable AG framework concepts) or the per-row _item.dart (a real reusable component). buildSuccess is inline in the page again: a collection page directly constructs AgListBuilder referencing _item.dart; a detail page keeps its original inline placeholder.

  • Fixed: ProjectAnalyzer.dependenciesResolved never detected resolution for a native-pub-workspace member project — it only checked <project root>/.dart_tool/package_config.json, but a workspace member's resolved config lives only at the workspace root, never duplicated per member. ag analyze run from inside such a project always silently skipped the resolved-model checks, even fully resolved. Fixed with package:package_config's findPackageConfig (walks up parent directories, mirroring how Dart's own tooling resolves package configs for workspace members), added as a direct dependency. Found by generating packages/ag_flow/example — a real workspace member — and running ag analyze from inside it directly.

  • Fixed two very_good_analysis findings against generated code, found the same way: a required named constructor parameter declared after an optional one (super.key) in every chrome component with a required field — reordered required-first; and Get.toNamed(...)'s untyped generic call in every generated navigation method — now emits Get.toNamed<dynamic>(...). Three more lints (flutter_style_todos, always_use_package_imports, discarded_futures) don't fit generated/ aggregator-file conventions at all and are disabled, with a rationale comment, in packages/ag_flow/example's own analysis_options.yaml rather than papered over in the shared root config every package uses.

  • Every generated module now lives under a shared lib/modules/ parent — lib/modules/product/..., lib/modules/auth/... — instead of directly under lib/, sitting alongside (never inside) lib/core. Filesystem organization only: route paths, class names, and every other naming derivation are unchanged. ag analyze's structural and resolved-model checks look under lib/modules/ accordingly. Found and fixed along the way: Mason's default {{var}} interpolation HTML-escapes its value, so the new module_import_path var (the first brick var whose value contains a /) rendered as modules&#x2F;product in every brick-templated import until switched to unescaped triple-mustache ({{{module_import_path}}}).

  • ag g m's generated page is now pure wiring — every overridable slot is its own component file, generated and linked in automatically. appBar, loadingBuilder, errorBuilder, and emptyBuilder each get a dedicated file under components/<namespace>/ (_appbar.dart, _loading.dart, _error.dart, _empty.dart), and the success content does too: a collection module's _list.dart (wraps AgListBuilder, referencing the existing _item.dart per-row widget — previously generated but never actually wired into the page) and a detail module's _view.dart (previously generated but likewise dangling). All are plain starting-point widgets — customize freely, or delete one and its one-line reference in the page to fall back to AgBasePage's own default for that slot.

    • Found and fixed along the way, via a real dart analyze against a freshly generated, pub get-resolved project (this repo's own verification convention, but not previously re-run after the add/update/delete stub feature below shipped): the generated Controller's update() stub silently collided with GetxController .update() — an override with an incompatible signature, a genuine invalid_override compile error in every project that generated one. Renamed to updateItem on the Controller only (Service/Repo keep update() — they don't extend GetxController, so there's no clash).
  • ag g m now generates add/update/delete stubs by default across Service/Repo/Controller, alongside the always-present read method (getPage/getByArgument) — matching the read method's own shape exactly (a plain UnimplementedError placeholder with a TODO pointing at core/endpoints.dart). Not a mixin, not framework-owned: a generator's whole point is to hand over more working boilerplate than starting from scratch, and every stub is deleted exactly as freely as any other generated code if a module doesn't need it. add is only generated for collection modules — a detail module has no "create a new one" concept. New --methods= flag controls which get generated (--methods=add,delete, or --methods=none for read-only); omitting it generates all applicable ones. Golden fixtures regenerated from real tool output, not hand-transcribed, matching this repo's own existing convention for avoiding formatter-output guessing errors.

  • Fixed: --plural= was documented and fully implemented at the ModuleGenerator/ModuleSpec layer, but never wired into ag g m itself — ModuleCommand never registered the option, so it was unreachable from the actual CLI. Wired via argParser.addOption ('plural', ...); the supplied override is run through the same pascalCase conversion the naming machinery already uses for every other segment, since an early version of this fix passed the raw CLI argument straight through and produced companiesController instead of CompaniesController. Covered by a new test/src/commands/module_command_test.dart, exercising the real args-parsing/CommandRunner path rather than only the generator API directly (which is exactly where the wiring gap — and the casing bug — were both invisible before).

  • ag analyze v2 checks — dependency-direction violations and unused detail arguments, both previously deferred pending a resolved element model. Built on package:analyzer's AnalysisContextCollection (resolved units, not just parseString) instead of the syntax-only approach every other check uses:

    • Dependency-direction: flags a Page directly referencing a Repo or Service, a Controller directly referencing a Service/ApiProvider/ Dio, or a Repo directly referencing ApiProvider/Dio — matched against the referenced type's full supertype chain (via InterfaceType.allSupertypes), so a concrete ProductsRepo is caught the same way a raw AgBaseRepo reference would be (requirments/ag_framework.md §11/§70).
    • Unused detail argument: flags a detail controller (one extending AgDetailController) whose inherited arguments accessor is never referenced anywhere in the class — resolved-model-based specifically so a reference is confirmed to resolve to the real inherited getter, not just a same-named local that would false-positive a syntax-only search.
    • Both checks are skipped (not a failure) if the project's dependencies haven't been resolved yet (no .dart_tool/package_config.json) — ag analyze prints a notice telling the user to run pub get first, while every other (syntax-only) check still runs normally.
    • Verified end-to-end via dart run and a real dart pub global activate-installed ag against a genuine flutter pub add-wired project. A standalone dart compile exe build does not work for these two checks — the analyzer can't auto-detect the Dart SDK from a self-contained native binary — documented as a known limitation since that isn't this package's supported distribution method anyway.
    • ProjectAnalyzer.analyze() is now async (Future<List<AnalyzeIssue>>) to support this; every existing syntax-only check is unaffected and every existing test still passes unchanged.

0.1.0 #

Initial release.

Commands

  • ag init — bootstraps lib/core/ skeleton (arguments.dart, endpoints.dart, routes/{app_routes,app_pages,route_management}.dart, bindings/initial_binding.dart). Idempotent — safe to re-run; existing files are never overwritten.
  • ag g m <path> (ag generate module) — generates the complete module for a collection (root) or detail (child) path:
    • Five architectural-layer files: page, controller, repo, service, binding.
    • Per-row item component and four override-slot components (appbar, loading, error, empty).
    • Idempotent wiring into app_routes.dart, app_pages.dart, route_management.dart, and (for detail modules) arguments.dart.
    • --model=Name / --from-json=sample.json — generates a typed model class and threads it through all five layers; handles nested objects, lists, snake_case / camelCase / kebab-case keys, Dart keyword collisions, and null sample values.
    • --plural=override — overrides the default pluralisation for the collection-layer class and file names.
    • --dry-run — prints what would be written without touching the filesystem.
  • ag analyze — validates a project against AG's structural rules:
    • Missing architectural-layer files.
    • Routes missing navigation wiring, argument class, or nav method.
    • Duplicate route paths.
    • Hard-coded AgNavigator.toNamed('/literal') calls outside route_management.dart.
    • (Resolved-model) Dependency-direction violations: Page→Repo, Page→Service, Controller→Service, Repo→ApiProvider.
    • (Resolved-model) Detail controller whose inherited arguments getter is never used.
  • ag --version — prints the CLI version.

Generator quality

  • All output goes through the same FileOp/Executor boundary — dry-run mode is not an afterthought.
  • Aggregator file updates are offset-splice AST edits (syntax-only package:analyzer) — an existing hand-customised navigation method survives regeneration byte-for-byte.
  • RouteConflictException — a route constant pointing at a different path than this module would derive is reported as a distinct error, never silently overwritten.
  • Parent-existence validation for child/detail modules — partial writes never happen on failure.
  • Golden fixtures regenerated from real tool output, not hand-transcribed.
  • Fully cumulative naming derivation (ModulePath/ModuleSpec), with root-module layer pluralization handled separately from (never- pluralized) component naming.
  • Parent-existence validation for child/detail modules, with the exact spec-mandated error message and no partial writes on failure.
  • --dry-run support; idempotent re-generation (never overwrites an existing file).
  • Idempotent updates to the shared aggregator files (app_routes.dart, app_pages.dart, route_management.dart, and — for detail modules — arguments.dart) via offset-splice AST editing (package:analyzer, syntax-only parse): a new entry is inserted only if missing; an existing entry — including a hand-customized navigation method — is left byte-for-byte untouched. Requires the project to already have the ag init skeleton in place (that command itself doesn't exist yet).
  • RouteConflictException: a route constant that already exists pointing at a different path than this module would derive is reported as a distinct conflict, never silently overwritten.
  • ag init: bootstraps a bare project's lib/core/ skeleton (arguments .dart, endpoints.dart, routes/{app_routes,app_pages,route_management} .dart, bindings/initial_binding.dart) so ag g m has somewhere to wire generated modules into. Idempotent via the same FileOp/Executor boundary as ag g m — an existing file is skipped, never overwritten. Verified end-to-end against a real flutter created app: ag init → ag g m product → ag g m product/details → flutter analyze --fatal-infos reports zero issues.
  • ag analyze: a v1, mechanical/structural-only validator. Re-parses app_routes.dart's route table and re-derives each route's expected ModuleSpec from its path (not its constant name, which is what makes this immune to whatever pluralizer produced the name), then checks: missing architectural-layer files, missing GetPage/nav-method/argument- class wiring, two route constants pointing at the identical path, nested architectural/component folders, and hard-coded Get.toNamed('/literal') calls outside route_management.dart. Exits 0 clean, non-zero with every issue printed otherwise. Dependency-direction violations and detail-route-without-argument-usage checks are deliberately deferred to v2+ (need a resolved element model, not just syntax) — a regression test confirms a Page hand-edited to call a Service directly produces zero issues today, documenting that boundary explicitly rather than leaving it implicit.
0
likes
0
points
343
downloads

Publisher

unverified uploader

Weekly Downloads

The `ag` CLI for ag_flow — one command generates a fully-wired feature module (page, controller, repo, service, binding, component) and wires its route, argument, and navigation method automatically.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

analyzer, args, dart_style, io, mason, mason_logger, meta, package_config, path, pluralize, yaml

More

Packages that depend on ag_flow_cli