Skip to content

feat(nonstop_cli): generate a production-shaped app skeleton - #40

Merged
ProjectAJ14 merged 7 commits into
mainfrom
feat/nonstop-cli-app-skeleton
Sep 5, 2026
Merged

ProjectAJ14 merged 7 commits into
mainfrom
feat/nonstop-cli-app-skeleton

Conversation

@ProjectAJ14

Copy link
Copy Markdown
Contributor

What

nonstop create <name> produced four empty directories and a melos.yaml. Everything a real app needs — logging, DI, theme, routing, networking, auth — was still a day of copy-paste from the last project. This ports that groundwork into the mono-repo brick and asks which parts you want.

? Which modules should youtube start with?
❯ ◉ Network        ◉ Analytics       ◉ Developer tools
  ◉ Authentication ◉ Crash reporting ◉ Dashboard shell
  ◯ Cloud Firestore ◉ Feature flags
  ◉ Push notifications

Non-interactive: -y for the recommended set, or per module (--no-notifications --firestore). Description and org name are prompted for when not passed.

Always generated: core (logger, DI bootstrap, route observers, error types, BLoC observer), di, design_system (Material 3 theme, components, toasts, loaders), localization (type-safe strings via i69n), utils, and the app shell — bootstrap, router, splash, with no business logic.

Optional: network, auth, firestore, notifications, analytics, crashlytics, feature_flags, developer, dashboard.

How the optional modules work

A directory named {{<module>_dir}} renders to an empty path segment when its module is off, and mason skips every file underneath it. Section tags can't be used for this — {{/x}} contains a path separator — so pre_gen derives the variables instead. It also resolves the two dependencies between modules (notifications → network, developer tools → feature flags) and reports them rather than letting them fail at compile time; the CLI applies the same rules so mason make on its own still produces something that builds.

flutter create moved into pre_gen so the brick's lib/ and pubspec.yaml overlay it. post_gen formats the result — mustache line breaks depend on the answers, and a fresh project has to pass its own melos lint.

Two things worth a reviewer's attention

Process failures were silent. cli_core's trackOperation reports success as soon as the future completes, without checking the exit code — a failed flutter create or melos bootstrap showed a green tick and generation carried on to produce a half-built workspace. Fixed locally in the brick's hooks. The right home is cli_core itself, since all four bricks have this, but hooks resolve it from pub so that fix can't land until it's published.

The brick is now 2.0.0. The CLI tries BrickHub first at ^<version>; no 2.x is published there, so it falls through to the bundled copy. Publishing a 2.x brick to BrickHub later would silently take over from the bundle.

Verification

Ten module combinations, each generated end to end:

melos bootstrap / melos lint / melos test pass on all ten
flutter build web ✓ Built build/web
Runtime boot app reaches its first screen; shipped as a smoke test
Repo melos lint / nonstop_cli tests SUCCESS / 18 passed

Non-Firebase projects ship apps/<name>/test/smoke_test.dart, which runs the real bootstrap.init() and pumps the app — it catches the class of bug that compiles fine and fails on device. Firebase projects can't boot before flutterfire configure, so the filename renders empty and mason drops it.

That smoke test earned its place during review, which turned up several bugs that passed analyze and melos lint cleanly: the auth screen crashed on an asset the template doesn't ship, Google/Apple buttons rendered as tofu because firebase_ui_auth never registers its own font, push notifications were never initialised when the dashboard was off, and a quote in --description produced an unparseable project.

Note on provenance

The groundwork was adapted from an existing production app. Its domain had settled into shared code — survey and health analytics groups, a survey event on core's global event bus, a hardcoded survey deep link in the notification client. All removed; verified by decoding all 222 files embedded in the generated bundle rather than grepping the source, since the bundle is what actually ships.

🤖 Generated with Claude Code

ProjectAJ14 and others added 7 commits September 3, 2026 19:15
The mono-repo brick is about to gain ~170 Dart files under __brick__. They are
mustache templates, not parseable Dart, and `dart format` has no exclude of its
own, so the lint script now filters the paths before handing them over.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…brick

The brick created four empty directories and a melos.yaml. Everything a real
app needs was still a day of copy-paste from the last project, so this ports
that groundwork in, adapted from an app that had already grown it.

Always generated: core (logger, DI bootstrap, route observers, error types,
BLoC observer), di, design_system (Material 3 theme, components, toasts,
loaders), localization (type-safe strings via i69n), utils, and the app shell
itself - bootstrap, router and splash, with no business logic.

Optional, each gated on a brick variable: network, auth, firestore,
notifications, analytics, crashlytics, feature_flags, developer, dashboard.

A directory named `{{<module>_dir}}` renders to an empty path segment when its
module is off, and mason skips every file underneath it - section tags cannot
be used for this because `{{/x}}` contains a path separator. pre_gen derives
those variables, resolves the two dependencies between modules and runs
`flutter create` so the brick's own lib/ and pubspec.yaml overlay it. post_gen
formats the result, because mustache line breaks depend on the answers and a
fresh project has to pass its own `melos lint`.

Every process step now checks its exit code. cli_core's trackOperation reports
success as soon as the future completes, so a failed `flutter create` or
`melos bootstrap` showed a green tick and generation carried on to produce a
half-built workspace.

Non-Firebase projects ship a boot test that runs the real bootstrap and pumps
the app; Firebase ones cannot until `flutterfire configure` has been run, so
the file name renders empty and mason drops it.

Verified across ten module combinations: melos bootstrap, melos lint and melos
test all pass, `flutter build web` succeeds, and the app reaches its first
screen at runtime.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`create` took a name, a description and an org. The brick now has nine optional
modules behind it, so it asks - one multi-select, defaults pre-ticked - and
prompts for the description and org name when they were not passed.

Explicit `--network` / `--no-network` style flags always win, `-y` takes the
recommended set, and a missing or undrivable terminal falls back to defaults so
scripts and CI keep working. Both ends have to be a terminal: mason_logger
reads stdin but draws on stdout, and piping either one breaks it.

Two module dependencies are applied and reported rather than left to fail at
compile time: notifications needs network, developer tools need feature flags.
The brick's hooks apply the same rules, so `mason make` on its own still
produces something that builds.

The description is validated like the name and org already were. It is written
into YAML, Dart string literals and Markdown, and a quote or a dollar sign
silently produced a project that does not parse.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Regenerated with `mason bundle` so the CLI ships the new template. The registry
lookup for the brick is version-pinned and the brick is now 2.0.0, so this
bundle is what `nonstop create` actually uses.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The brick was ported from a health app, and the rename pass only caught the
product name. Its domain had also settled into shared code, where it would
have shipped to every project generated from the template.

Removed: survey and health event groups from analytics, along with the two
survey helpers on AnalyticsHelper; a survey-assignment event on core's global
event channel, replaced with a single generic example; and the source
project's own examples throughout the analytics README.

The notification client hardcoded one deep link - a `surveyAssignments`
message type routed to `/home/survey/assignment/$id`, a path this template
does not even define. It now reads a `route` from the message data and pushes
that, which is the contract the docs can actually describe.

The auth header is drawn from the theme instead of fetching a placeholder
image, so a fresh project has no artwork to ship and nothing to fetch.

Also corrects the CI package filter, which excluded a workspace named after
the source project and so never matched anything here.

Verified by decoding all 222 files embedded in the generated bundle: no
product-specific term survives in what the CLI actually ships.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The workflow runs `dart format .` directly rather than the melos lint script,
so excluding __brick__ there was not enough - the job still parsed the
mustache templates and failed with exit 65.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ProjectAJ14
ProjectAJ14 merged commit 9250985 into main Sep 5, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant