# The `smf create` command

```bash
smf create <app name> [options]
```

`smf create` generates a Flutter app from modules in a new directory. Besides `smf help`, it is the only command of `smf`. `smf --version` prints the version, and `smf create --help` lists the options.

## Options

| Option | What it does |
| --- | --- |
| `-m`, `--modules=<module,module>` | The modules to add, separated by commas, such as `-m home,bottom_tabs`. The modules they need are added too. Without it, a run in a terminal asks for the modules. |
| `--org=<com.example>` | The organization in reverse domain notation. It starts the identifiers of the app. Without it, a run in a terminal asks; otherwise it is `com.example`. |
| `-o`, `--output=<directory>` | The directory in which the app's own directory is created. The default is the current directory. |
| `--on-conflict=<prompt\|replace\|copy\|cancel>` | What to do when the app's directory exists and is not empty; see [below](#an-existing-directory). The default is `prompt`. |
| `--strict` | Stop instead of leaving out a module that cannot work, and on a warning about what the app is made of; see [lenient and strict](#lenient-and-strict). |
| `--explain` | Print what would be generated and whether the machine is ready, then stop without changing anything; see [below](#explain). |
| `--skip-external-setup` | Never install tools, log in or configure external services; print what to run instead. |
| `--[no-]input` | Whether the run may ask questions in the terminal. With `--no-input`, everything comes from the options. |
| `--start=<path>` | The full path of the screen the app starts on, such as `/home`. It can name any route without required parameters that is outside the flow of a [guard](https://doc.saymyframe.com/guides/navigation.md#guards) and asks for no condition, and a run without a terminal needs it when several screens can start the app. An option of the router role. |
| `--auth-mode=<required\|guest\|anonymous>` | Who may use the app without an account: nobody (`required`, the default), everyone (`guest`), or everyone as an anonymous user (`anonymous`); see [Sign-in](https://doc.saymyframe.com/guides/sign-in.md#the-mode). An option of the auth role. |
| `--locales=<codes>` | The languages of the app among those of the texts of its modules, such as `en,uk`. The first is the one the app uses when the device asks for none of them. The default is all of them, English first; see [Languages](https://doc.saymyframe.com/guides/languages.md#choosing-the-languages). An option of the localization role. |
| `--verbose` | Report more of what happens. It can come before or after `create`. |
| `-h`, `--help` | Print the usage of the command. |

## The name and the identifiers

The app name becomes the Dart package name of the app in snake_case, so `My App`, `myApp` and `my-app` all become `my_app`. The package name is also the name of the app's directory under `--output`. The package name must start with a Latin letter and contain only letters, digits and underscores. It cannot be a keyword of Dart, Java or Kotlin, or the name of a package every Flutter app depends on, such as `flutter` or `collection`.

Every part of the organization starts with a letter and has only letters, digits, hyphens and underscores, and no part is a keyword of Java or Kotlin. The identifiers of the app join the organization and the package name:

| | `smf create my_app --org com.example` |
| --- | --- |
| Android application id and namespace | `com.example.my_app` |
| iOS bundle id | `com.example.my-app` (bundle ids take hyphens, not underscores) |

These are the ids of the flavor `prod`. The flavors `dev` and `staging` add `.dev` and `.staging`, so all three install next to each other; see [Flavors](https://doc.saymyframe.com/guides/flavors.md).

## Questions

`smf create` asks questions only when it runs in a terminal, without `--no-input` and without `--explain`. It asks for:

1. the app name, unless it is given;
2. what to do with an existing directory, unless `--on-conflict` says;
3. the organization, unless `--org` gives it;
4. the modules, unless `-m` gives them: first the modules without a role by kind, such as the features, with `home` selected already, then a provider for each role, where Enter takes the first one (see [Create your first app](https://doc.saymyframe.com/getting-started/first-app.md#the-questions) and [the order of the roles](#the-order-of-the-roles));
5. the provider of a role that the chosen modules require and that several modules provide, as the state manager for the screens of `sign_in`. Such a question names the module that requires the role and offers no None;
6. whether to set up what a check of the machine found missing, such as the Firebase CLI, unless the run skips external setup (see [the machine and external setup](#the-machine-and-external-setup));
7. what the roles of the app leave open: the start screen, when several screens can start the app and `--start` does not say, and who may use the app without an account, in an app with a module that provides sign-in, unless `--auth-mode` says;
8. whether to run a step of a module after generation now, such as the tool that makes the app icon, or `dart tool/configure_firebase.dart dev` and after it the step that enables the sign-in methods in Firebase.

Without a terminal, or with `--no-input`, the run asks nothing. Where it would have to ask, it stops with a usage error that says what to add: the app name, `--on-conflict`, a provider in `-m`, or `--start`. This is the error of `-m home,sign_in` without a state manager:

```text
Several modules provide the state management role, which sign_in requires: bloc, riverpod. Add one of them to -m.
```
 It does not stop for `--auth-mode`: without it, the app is in the mode `required`. Without `-m`, the app gets only what every app needs: a run without a terminal never takes a module that it was not given, so it does not take `home` either.

### The order of the roles

The question of each role comes after the questions of the roles that require it, so that a role that an earlier answer needs offers no None. A role that an earlier answer requires and that only one module provides gets no question at all: `smf create` adds that module and says why. The questions come in this order, and a run asks those that the answers before leave open:

1. Features, and Infrastructure: the modules without a role.
2. Layout, Settings screen and Onboarding screen. Each requires the router, so the router has no question once one of them is chosen.
3. Router, when no earlier answer requires it.
4. Localization and Theme.
5. Push messages, and then Notifications, which push messages require.
6. Preferences, Dependency injection, State management and Events.
7. Authentication, and then REST API, which uses it.
8. Purchases, and then Values in .env, which the modules of the two roles before it read.
9. Crash reporting and Analytics, each of which takes several modules.
10. App icon, Splash screen and CI.

So the questions go from what the user of the app sees to what gets the app released. Enter takes the first module of a question that takes one answer. So a run in a terminal that answers every question with Enter gets a provider for each of those roles, among them the app icon and the splash screen.

The notifications role adds a step to the onboarding and an entry to the settings screen, so both screens are decided by the time of its question. In an app with every module, the onboarding, the theme, the localization and the notifications require the preferences, so `shared_preferences` comes without a question:

```text
Adding shared_preferences: the only provider of the preferences role, which onboarding requires.
```

## An existing directory

When the app's directory exists and is not empty, `--on-conflict` decides:

| Value | What happens |
| --- | --- |
| `prompt` | A run in a terminal asks what to do. The default answer keeps the directory and creates the app next to it; the others replace the directory or cancel the run. Without a terminal, the run stops with a usage error. |
| `replace` | SMF generates the app first. Then it sets the old directory aside, moves the app into its place and deletes the old directory. If moving the app fails, the old directory goes back. |
| `copy` | The app goes into a new directory next to it: `my_app copy`, or `my_app copy 2` if that exists too. |
| `cancel` | Nothing is generated, and `smf create` exits with code 1. |

SMF decides this before it does anything else, such as installing a tool.

## Lenient and strict

By default, `smf create` is lenient. When a module cannot work in the app, such as a feature whose tab does not fit into the tab bar, it leaves that module out with a warning, plans the app again without it, and generates the rest. At the end it warns again about the modules the app is without.

A run can also warn about what the app is made of while every module stays in it, such as an app whose routes mark none as a start screen. A lenient run prints such a warning and goes on, with the role that reported it:

```text
[WARN] [role:router]: No route is marked as a start candidate, so the app starts on its fallback screen. Choose a start route with --start.
```

With `--strict`, a problem of a module stops the run with exit code 1 before anything is generated, and so does a warning about what the app is made of. A warning of the checks of the module model stops the run before the machine is checked, and a warning of a choice of a role, such as the start screen, stops it before the app is rendered. Use `--strict` in scripts and CI, where an app without a module you asked for, or an app that does not come out as you asked, is a failure. This is a strict run of an app with `settings` alone, which has no screen to start on and no main navigation that leads to the settings screen:

```text
Generation stopped because of 2 warnings about the app. Without --strict, the run goes on after them.
  warning [role:router] router.no_start: No route is marked as a start candidate, so the app starts on its fallback screen. Choose a start route with --start.
  warning [role:router] router.no_main_navigation: No module provides the layout role, so the app has no main navigation to lead to the destination /settings (settings). Choose a provider of the role, or open it from a screen of yours with context.nav.
```

A warning about the machine, such as a tool that a step after generation needs, never stops a run, strict or not.

Each warning about the app has a code: the id of the role that reports it, a dot, and a name. The built-in roles report these:

| Code | What the app is made of |
| --- | --- |
| `router.no_start` | The app has routes, but no route can start it, and `--start` names none. It starts on the [fallback start screen](https://doc.saymyframe.com/modules/flutter-core.md#the-fallback-start-screen). |
| `router.no_main_navigation` | The app has destinations besides the one that it starts on, but no layout, so no main navigation leads to them. |
| `router.ambiguous_routes` | Two routes of a module both match some locations, which go to the route declared first. |
| `localization.untranslated` | Texts of a module have no translation into a language of the app, which shows them in English there. |
| `localization.unsupported_language` | A text of a module has a translation into a language that no app can be in, so the app leaves that language out. |
| `localization.unnamed_language` | The setting of the language shows a language of the app by its code, since the role has no name for it. |
| `auth.open_account_routes` | Routes ask for an account, but no guard of the app stands for it, so every user gets to them; see [Sign-in](https://doc.saymyframe.com/guides/sign-in.md#an-app-without-the-screens). |
| `auth.no_equivalent_sign_in` | On iOS, the app offers a sign-in of a third party, such as Google, and no way to sign in that the App Store takes as its equivalent, as its guideline 4.8 asks; see [Sign-in](https://doc.saymyframe.com/guides/sign-in.md#ways-to-sign-in). |
| `pipeline.unused_option` | An option of a role that the app does not have, which nothing reads. |

`smf create` has a hidden option for apps that warn on purpose, as some apps of SMF's own CI do: with `--strict`, `--allow-warning=<code>` lets the run go on after a warning with that code, which it still prints. Give it once for each code. A value that is not the form of a code is a usage error.

Some problems stop the run in either mode. A problem that no single module causes, such as a usage error, is one of them. So is the problem of a module that no app can be made without, such as `flutter_core`, the only provider of the app entry. `smf create` does not leave such a module out, and stops with its problem and how to fix it, such as a Flutter SDK that is too old.

## Explain

`--explain` runs everything that decides the app, which means the selection of modules, the checks of the module model and the checks of the machine, and prints the result. It never asks, installs or logs in, and it generates no file. Where a real run would ask which module provides a role, it takes the first one and names the others. It makes no choices either, so it shows the warnings of the checks of the module model and not those of the choices, and with `--strict` it stops only on the first.

```bash
smf create my_app --org com.example -m home,bottom_tabs,get_it,event_bus --explain
```

```text
App my_app of com.example
  Android application id: com.example.my_app
  iOS bundle id: com.example.my-app
  Directory: /Users/you/projects/my_app

Modules
  home: requested
  bottom_tabs: requested
  get_it: requested
  event_bus: requested
  flutter_core: the only provider of the app entry role, which every app needs
  go_router: the only provider of the router role, which home requires

Roles
  Layout: bottom_tabs
  Dependency injection: get_it
  Events: event_bus
  App entry: flutter_core
  Router: go_router

Dependencies
  get_it ^9.3.0 (get_it)
  event_bus ^2.0.1 (event_bus)
  flutter from the flutter SDK (flutter_core)
  go_router ^17.5.0 (go_router)

Dev dependencies
  flutter_test from the flutter SDK (flutter_core)
  flutter_lints ^6.0.0 (flutter_core)

Machine
  ✓ Flutter SDK
```

Depending on the app, the report has more sections. "Left out (lenient mode)" lists the modules that the app would be without, and why. "Order of contributions" shows, for code that several modules put into the same place, the order they come in and the reason for it. "After generation" lists the commands that would run in the new app, such as `dart tool/configure_firebase.dart dev` of the Firebase modules, each step that continues another under it, and the notice of a step that has one. This is that section for an app with `sign_in`:

```text
After generation
  dart tool/configure_firebase.dart dev (firebase_core)
    then dart tool/enable_firebase_sign_in.dart (firebase_auth)
      If the app signs in with Google, it also gives Google the app's name and the Firebase CLI account's email for its consent screen, registers the SHA-1 and SHA-256 of this machine's Android debug key (creating the key if there is none, as the first debug build would), downloads the app's Firebase files again and writes the iOS URL scheme into ios/Runner/Info.plist.
      A run asks before it runs this step, and leaves it for later when it cannot ask.
```

"By hand" lists the [steps by hand](#steps-by-hand) of an app that has any, after "After generation": the link to their guide, then each step with its id, its title and, in parentheses, who lists it. These are the lines of an app with `apple_sign_in`:

```text
By hand
  https://doc.saymyframe.com/setup?steps=app_entry.ios_app_id,firebase_auth.apple_provider,firebase_auth.apple_revocation
  app_entry.ios_app_id: In Xcode, sign the iOS app with your paid Apple Developer team (apple_sign_in)
  firebase_auth.apple_provider: Turn on Sign in with Apple in Firebase Authentication (firebase_auth)
  firebase_auth.apple_revocation: Give Firebase your Sign in with Apple key, so that deleting an account works (firebase_auth)
```

"Machine" gets a line for each check a module needs, with instructions for what is missing. These are the lines of the same app on a machine without the Firebase CLI:

```text
Machine
  ✓ Flutter SDK
  ✗ Firebase CLI (for firebase_core): missing
    Install it with "npm install -g firebase-tools", or see https://firebase.google.com/docs/cli.
    An interactive run offers to set it up.
  ✗ Firebase login (for firebase_core): missing
    Install the Firebase CLI, then log in with "firebase login", or on a remote machine, such as over SSH, with "firebase login --no-localhost".
  ✓ FlutterFire CLI 1.4.1 or a later 1.x (for firebase_core)
  ✓ Xcode project tools of flutterfire (for firebase_core)
  ✓ Setup of the Xcode project on a Mac (for firebase_core)
  ✗ Firebase CLI 15.34.0 or later (for firebase_auth): missing
    Install it with "npm install -g firebase-tools", or see https://firebase.google.com/docs/cli.
```

## The machine and external setup

Before it generates anything, `smf create` checks the machine: the Flutter SDK for every app, and whatever the modules need, such as the Firebase CLI for the Firebase modules. The Flutter SDK must satisfy the SDK constraints of the app, which means Flutter 3.44 or newer, or the run stops, as [troubleshooting](https://doc.saymyframe.com/guides/troubleshooting.md#the-flutter-sdk-is-missing-or-too-old) shows.

SMF checks the machine for a module after the modules it depends on, wherever you named them, so a check can count on a tool that an earlier one installed.

When something is missing, the checks of the built-in modules only warn and print instructions. In a terminal, SMF offers to set up what it can. Each question says what the check found, such as a tool that is missing or an older version of it, and how to set it up by hand, then asks whether to set it up now. With `--skip-external-setup`, it never installs anything, logs in or sets up an external service, and prints the instructions instead.

Some modules run a command in the new app, such as `dart tool/configure_firebase.dart dev`. A module marks such a step as skippable when the app is complete without it, as `firebase_core` does. A skippable step may not run, because the run has no terminal, skips external setup or lacks a tool the step needs. It may also fail, or you may leave it for later. In each case `smf create` still creates the app and prints the command to run in it later, with the reason.

Nothing went wrong when a step is left for later, so the run does not warn of it. That is a step that you chose to run later, a step that a run without a terminal or with `--skip-external-setup` holds back, and a step that continues one of these. The run lists such steps after the line that the app is created, in the order in which they run. In an app with [steps by hand](#steps-by-hand), the line about them follows the list, and the lines on running the app come last. When the command needs a tool that the machine lacks, the reason names that tool too, as on a machine without the Firebase CLI:

```text
Created my_app in /Users/you/projects/my_app.
One step is left for later. Run it in the app:
  Configuring Firebase with flutterfire (the run skips external setup, and Firebase CLI and Firebase login are missing):
    dart tool/configure_firebase.dart dev
Then run the app:
  cd /Users/you/projects/my_app
  flutter run
```

The run warns of a step when its command fails, or when it would have run the step and a tool that the step needs is missing, as after you decline to install the Firebase CLI in a terminal. The warning comes before the line that the app is created:

```text
[WARN] Configuring Firebase with flutterfire is not done, because Firebase CLI and Firebase login are missing. Run it in the app: dart tool/configure_firebase.dart dev
```

A step may come with a notice: something to know before it runs that its description does not tell, such as a side effect of the tool that it runs. A run in a terminal asks before it runs such a step, with the notice in the question, and a run that cannot ask leaves the step for later. The notice follows the command of a step that is left for later, and `--explain` shows it under the command.

The step of `firebase_auth` that enables the sign-in methods in the Firebase project is such a step, and it continues the configuration of Firebase. When that command is left for later, the step waits with it. The list then has both, and the step refers to the command that it continues by its number:

```text
2 steps are left for later. Run them in the app, in this order:
  1. Configuring Firebase with flutterfire (the run skips external setup, and Firebase CLI and Firebase login are missing):
     dart tool/configure_firebase.dart dev
  2. Enabling the sign-in methods of the app in its Firebase project (it runs after step 1):
     dart tool/enable_firebase_sign_in.dart
     If the app signs in with Google, it also gives Google the app's name and the Firebase CLI account's email for its consent screen, registers the SHA-1 and SHA-256 of this machine's Android debug key (creating the key if there is none, as the first debug build would), downloads the app's Firebase files again and writes the iOS URL scheme into ios/Runner/Info.plist.
```

A step that the app cannot do without either runs or stops the run.

## Steps by hand

Some of the setup of an app is out of reach of any command, such as uploading the APNs key of push messages to Firebase, or signing the iOS app with a paid Apple Developer team. The modules of the app list such steps, and the run ends with them, after the steps that are left for later. This is the end of a run for an app with `firebase_analytics` that skips external setup:

```text
Created my_app in /Users/you/projects/my_app.
One step is left for later. Run it in the app:
  Configuring Firebase with flutterfire (the run skips external setup, and Firebase CLI and Firebase login are missing):
    dart tool/configure_firebase.dart dev
Finish the setup by hand in one step:
  https://doc.saymyframe.com/setup?steps=firebase_analytics.google_analytics
Then run the app:
  cd /Users/you/projects/my_app
  flutter run
```

A service knows each [flavor](https://doc.saymyframe.com/guides/flavors.md#what-repeats-for-each-flavor) of the app as an app of its own, so such a step repeats for each flavor that needs it.

The link opens [Finish the setup](https://doc.saymyframe.com/setup.md) with the steps of the app in the order to do them, each with a link to its guide, click by click. Do the steps once the commands that are left for later have run. The run asks nothing about these steps, so a run without a terminal, with `--no-input` or with `--skip-external-setup` prints the same lines. The README of the app lists the same steps, with the same link, in its first section, "Finish the setup". Without the link of a run, the page lists every step of the built-in modules.

## Exit codes

| Code | Meaning |
| --- | --- |
| `0` | The app was created, or `smf` printed help, the version or the report of `--explain`. |
| `1` | Generation failed, and the messages say why: an error of a module or a warning about the app with `--strict`, an error of a module that every app needs, a machine without what the app needs, such as a Flutter SDK that is missing or too old, a failed command, or `--on-conflict cancel`. If the app was generated already, it stays in a temporary directory that the message names. |
| `64` | The command line was wrong or incomplete: an unknown option or module, a missing app name, a value of `--allow-warning` that is not a code, or a question that a run without a terminal cannot ask. |
| `70` | An unexpected error, which is a bug of `smf` or of a module. Run again with `--verbose` for the full log, and please [report it](https://github.com/saymyframe/smf_flutter_cli/issues). |
| `130` | The run was cancelled with Ctrl-C, by the end of the input, with Cancel in the question about an existing directory, or, on macOS and Linux, with `SIGTERM`. Nothing of the app is left behind. |

## In scripts and CI

A run for a script names everything and asks nothing:

```bash
smf create my_app \
  --org com.example \
  -m home,bottom_tabs,get_it,bloc \
  --no-input \
  --skip-external-setup \
  --strict \
  --on-conflict replace
```

SMF's own CI generates its apps the same way, without a terminal and with `--strict`. It checks each app with `flutter analyze` and runs tests in it, such as the start-up of Firebase, and it starts apps on an Android emulator and an iOS simulator. Every night it does so on every stable release of Flutter that the apps allow.
