Skip to main content

Contributing

SMF is developed in the open at saymyframe/smf_flutter_cli. Bug reports and ideas go to GitHub Issues, and questions are welcome on Discord. This page is an overview, and the repository has the details. CONTRIBUTING.md describes the process, and AGENTS.md describes the layout, the rules and the conventions. AGENTS.md is written for coding agents, but it serves people just as well.

The repository​

The repository is a Dart pub workspace managed with Melos. Every package is versioned and published on its own.

packages/
smf_contracts/ the module model: modules, roles, sockets, contributions
smf_pipeline/ the stages of smf create and the contract harness
fixture_registry/ apps from the fixtures, their snapshots and their matrix
test/fixtures/ fake modules for features that no real module uses yet
smf_modules/
smf_<module>/ the modules: flutter_core, go_router, home, ...
smf_flutter_cli/ the smf command: its modules and the machine
tools/ bundling of bricks, the banlist, the version of the CLI,
the test of the package graph

Dependencies point one way: smf_contracts ← smf_pipeline and the modules ← smf_flutter_cli. The pipeline knows no module and no concrete role, and modules use smf_pipeline only in their tests.

Set up​

CI pins Dart 3.12.2, and the output of the formatter depends on the version, so use that Dart, with Flutter 3.44.2 for the apps. Then run:

dart pub global activate melos
dart pub global activate mason_cli
melos bootstrap

melos bootstrap resolves the workspace and bundles every brick into lib/bundles/ with the Mason CLI. The bundles are generated, so change the brick, bundle it again and commit both. CI fails when a committed bundle differs from its brick.

Checks​

melos run check

runs, in this order:

ScriptChecks
melos run format:checkThe formatting of the Dart code of every package, its app_tests/ and example/ included, and of tools/. melos run format formats it.
melos run analyzedart analyze --fatal-infos --fatal-warnings in every package and in tools/.
melos run banlistThat no file uses the names that the module model replaced; see tools/banlist.dart.
melos run testThe tests of every package and of tools/. The tests in tools/ also check that the published packages, dev dependencies included, do not depend on each other in a cycle. pub.dev resolves the dev dependencies of a package when it analyzes it.

Tests are hermetic. They use no network and no Flutter SDK, and they remove their temporary directories. A few tests need more and skip themselves without it. The test of smf_flutter_core that compares its brick with an app that flutter create wrote needs that app, which the CI job with Flutter for the modules of the CLI creates. Two others run only in the jobs on macOS and Windows, described below. A test of generated code parses or analyzes it rather than comparing strings. A bug that a change finds but does not fix gets a test of the correct behavior, marked skip: 'Bug: <description>'.

Snapshots​

The CLI and the fixture registry keep snapshots of the apps they generate, in packages/smf_flutter_cli/test/snapshots/ and packages/smf_pipeline/fixture_registry/test/snapshots/. When a change alters what an app renders, update the snapshots and review their diff:

cd packages/smf_flutter_cli
SMF_UPDATE_SNAPSHOTS=1 dart test test/snapshot_test.dart

Generated apps​

Besides the job of the checks above, CI has two jobs with Flutter 3.44.2, one for the modules of the CLI and one for the fixture modules. Each generates the apps of the contract harness for its modules, and the apps with every module, with smf create --no-input --skip-external-setup --no-dart-fix --strict, and runs flutter analyze on each of them. --no-dart-fix, a hidden option, skips the full dart fix of the app.

Then the job copies into the apps the tests that apply to them, from the app_tests/ directories of the packages, adds the packages these tests use with flutter pub add dev:…, runs flutter analyze again and then flutter test. These tests check what only a running app shows, such as the start-up of Firebase with the platform side of its plugins mocked, and they are not part of the apps that smf create generates. app_tests/ stays out of the analysis of its package, of its published archive and of SonarCloud, but melos run format formats it. Give each testWidgets there an explicit timeout.

To run the jobs locally, with flutter on the PATH:

dart run packages/smf_flutter_cli/tool/matrix.dart /tmp/smf_apps
dart run packages/smf_pipeline/fixture_registry/tool/matrix.dart /tmp/smf_fixture_apps

To check only some apps of the matrix, name them after the directory:

dart run packages/smf_flutter_cli/tool/matrix.dart /tmp/smf_apps 'every module (bloc)'

In a directory whose path has letters beyond ASCII, the matrix analyzes the apps with dart analyze --fatal-infos, because flutter analyze of Flutter 3.44 and 3.47 fails in such a directory on every system. CI runs the matrices in a directory with a space in its name, then one app of each in a directory whose name has letters beyond ASCII too.

To run the CLI from source:

cd packages/smf_flutter_cli
dart run bin/smf_flutter.dart create my_app -o /tmp/out --org com.example --no-input --on-conflict replace

macOS and Windows​

CI also runs on the macOS and Windows runners of GitHub, with Flutter 3.44.2. Both jobs run the tests of every package and of tools/. A test of something that only one system does runs only there, marked @TestOn('mac-os') or testOn: 'windows': on macOS, the Ruby of the machine and its gem xcodeproj; on Windows, the batch files that the CLI runs without a shell.

The macOS job generates an app without Firebase and one with every module, in a directory whose name has a space and letters beyond ASCII, and builds both for the iOS simulator. Then a test of smf_firebase_core archives the second app with flutter build ipa --no-codesign. Before the archive, it adds the build phase for Crashlytics with the gem xcodeproj, as flutterfire configure does, and fixes the phase with the command from the README of the app. The job names the app in SMF_CRASHLYTICS_APP.

The Windows job runs the matrix of the CLI for the apps with every module. It also generates an app as a user does, with the full dart fix, in a directory whose name has letters beyond ASCII. SMF generates the apps in %TEMP% and moves them to another drive. Last, the job runs the FlutterFire CLI through dart.bat, as SMF does.

Another Windows job runs the PowerShell script that installs the Firebase CLI, for real. The script changes the machine, so its test runs only when SMF_INSTALL_FIREBASE_CLI is 1.

Changes​

  • Keep modules independent. A change never teaches one module about another to fix a problem quickly. See Module independence.
  • Use Conventional Commits with the short name of the package as the scope, such as fix(go_router): … or feat(contracts): …. melos version derives the versions and changelogs from them, so don't bump versions or edit changelogs by hand.
  • Name branches fix/<topic>, feat/<topic>, docs/<topic>, test/<topic> or chore/<topic>. Keep a pull request to one concern and a commit to one logical change. Pull requests are squash-merged.
  • Once a branch is pushed, add new commits on top instead of rewriting it.