Metro CLI tool
- Introduction
- Install
- Make Commands
- App Icons
- Slate
- Custom Commands
Introduction
Metro is a CLI tool that works under the hood of the Nylo framework. It provides a lot of helpful tools to speed up development.
Install
Mac guide
Run the below command from the terminal
echo "alias metro='dart run nylo_framework:main'" >>~/.bash_profile && source ~/.bash_profile
If you open a project that uses Nylo, try to run the following in the terminal.
dart run nylo_framework:main
# or with the alias
metro
You should see an output similar to the below.
Usage:
command [options] [arguments]
Options
-h
All commands:
[Widget Commands]
make:page
make:stateful_widget
make:stateless_widget
make:state_managed_widget
make:navigation_hub
make:form
[App Commands]
make:model
make:provider
make:api_service
make:controller
make:event
make:theme
make:route_guard
make:config
make:interceptor
make:command
[Custom Commands]
motivation:quote
Windows Guide
- Open PowerShell as an administrator.
- Create a PowerShell profile if you don't have one:
if (!(Test-Path -Path $PROFILE)) {
New-Item -ItemType File -Path $PROFILE -Force
}
- Open the profile in a text editor:
notepad $PROFILE
- Add the following line to the profile:
function metro { dart run nylo_framework:main @args }
- Save the file and close the editor.
- Reload your PowerShell profile:
. $PROFILE
Make controller
Making a new controller
You can make a new controller by running the below in the terminal.
dart run nylo_framework:main make:controller profile_controller
# or with the alias metro
metro make:controller profile_controller
This will create a new controller if it doesn't exist within the lib/app/controllers/
directory.
Forcefully make a controller
Arguments:
Using the --force
or -f
flag will overwrite an existing controller if it already exists.
metro make:controller profile_controller --force
Make model
Making a new model
You can make a new model by running the below in the terminal.
dart run nylo_framework:main make:model product
# or with the alias metro
metro make:model product
It will place the newly created model in lib/app/models/
.
Make a model from JSON
Arguments:
Using the --json
or -j
flag will create a new model from a JSON payload.
dart run nylo_framework:main make:model product --json
# or with the alias metro
metro make:model product --json
Then, you can paste your JSON into the terminal and it will generate a model for you.
Forcefully make a model
Arguments:
Using the --force
or -f
flag will overwrite an existing model if it already exists.
dart run nylo_framework:main make:model product --force
# or with the alias metro
metro make:model product --force
Make page
- Making a new page
- Create a page with a controller
- Create an auth page
- Create an initial page
- Create a bottom navigation page
- Forcefully make a page
Making a new page
You can make a new page by running the below in the terminal.
dart run nylo_framework:main make:page product_page
# or with the alias metro
metro make:page product_page
This will create a new page if it doesn't exist within the lib/resources/pages/
directory.
Create a page with a controller
You can make a new page with a controller by running the below in the terminal.
Arguments:
Using the --controller
or -c
flag will create a new page with a controller.
dart run nylo_framework:main make:page product_page --controller
# or with the alias metro
metro make:page product_page -c
Create an auth page
You can make a new auth page by running the below in the terminal.
Arguments:
Using the --auth
or -a
flag will create a new auth page.
dart run nylo_framework:main make:page login_page --auth
# or with the alias metro
metro make:page login_page -a
Create an initial page
You can make a new initial page by running the below in the terminal.
Arguments:
Using the --initial
or -i
flag will create a new initial page.
dart run nylo_framework:main make:page home_page --initial
# or with the alias metro
metro make:page home_page -i
Create a bottom navigation page
You can make a new bottom navigation page by running the below in the terminal.
Arguments:
Using the --bottom-nav
or -b
flag will create a new bottom navigation page.
dart run nylo_framework:main make:page dashboard --bottom-nav
# or with the alias metro
metro make:page dashboard -b
Forcefully make a page
Arguments:
Using the --force
or -f
flag will overwrite an existing page if it already exists.
dart run nylo_framework:main make:page product_page --force
# or with the alias metro
metro make:page product_page --force
Make stateless widget
Making a new stateless widget
You can make a new stateless widget by running the below in the terminal.
dart run nylo_framework:main make:stateless_widget product_rating_widget
# or with the alias metro
metro make:stateless_widget product_rating_widget
The above will create a new widget if it doesn't exist within the lib/resources/widgets/
directory.
Forcefully make a stateless widget
Arguments:
Using the --force
or -f
flag will overwrite an existing widget if it already exists.
dart run nylo_framework:main make:stateless_widget product_rating_widget --force
# or with the alias metro
metro make:stateless_widget product_rating_widget --force
Make stateful widget
Making a new stateful widget
You can make a new stateful widget by running the below in the terminal.
dart run nylo_framework:main make:stateful_widget product_rating_widget
# or with the alias metro
metro make:stateful_widget product_rating_widget
The above will create a new widget if it doesn't exist within the lib/resources/widgets/
directory.
Forcefully make a stateful widget
Arguments:
Using the --force
or -f
flag will overwrite an existing widget if it already exists.
dart run nylo_framework:main make:stateful_widget product_rating_widget --force
# or with the alias metro
metro make:stateful_widget product_rating_widget --force
Make API Service
- Making a new API Service
- Making a new API Service with a model
- Make API Service using Postman
- Forcefully make an API Service
Making a new API Service
You can make a new API service by running the below in the terminal.
dart run nylo_framework:main make:api_service user
# or with the alias metro
metro make:api_service user_api_service
It will place the newly created API service in lib/app/networking/
.
Making a new API Service with a model
You can make a new API service with a model by running the below in the terminal.
Arguments:
Using the --model
or -m
option will create a new API service with a model.
dart run nylo_framework:main make:api_service user --model="User"
# or with the alias metro
metro make:api_service user --model="User"
It will place the newly created API service in lib/app/networking/
.
Use Postman to Make API Services
You can make new API services using Postman v2.1 collection files.
First, you must export your postman collection into a JSON file (using v2.1).
Copy the exported file into the public/assets/postman/collections
directory.
After copying the file into the above directory, go to the terminal and run the following command.
Arguments:
Using the --postman
or -p
option will create a new API service using a Postman collection.
dart run nylo_framework:main make:api_service my_collection_name --postman
# or with the alias metro
metro make:api_service my_collection_name --postman
This will prompt you to select a collection from the public/assets/postman/collections
directory.
Postman Environments
If your postman collection has an environment, export the file and add it to the public/assets/postman/environments
directory.
Then, run the below command.
dart run nylo_framework:main make:api_service collection --postman
# or with the alias metro
metro make:api_service collection --postman
It will prompt you to select an environment from the public/assets/postman/environments
directory.
What does it do?
It will also add Models to your project if your Postman collections have a saved response. You can check if your collection has saved responses by going to your Postman collection, then selecting a request. You should see a dropdown which will contain any saved responses.
The saved response name will be used for the Model name that Nylo will create.
Forcefully make an API Service
Arguments:
Using the --force
or -f
flag will overwrite an existing API Service if it already exists.
dart run nylo_framework:main make:api_service user --force
# or with the alias metro
metro make:api_service user --force
Make event
Making a new event
You can make a new event by running the below in the terminal.
dart run nylo_framework:main make:event login_event
# or with the alias metro
metro make:event login_event
This will create a new event in lib/app/events
.
Forcefully make an event
Arguments:
Using the --force
or -f
flag will overwrite an existing event if it already exists.
dart run nylo_framework:main make:event login_event --force
# or with the alias metro
metro make:event login_event --force
Make provider
Making a new provider
Create new providers in your application using the below command.
dart run nylo_framework:main make:provider firebase_provider
# or with the alias metro
metro make:provider firebase_provider
It will place the newly created provider in lib/app/providers/
.
Forcefully make a provider
Arguments:
Using the --force
or -f
flag will overwrite an existing provider if it already exists.
dart run nylo_framework:main make:provider firebase_provider --force
# or with the alias metro
metro make:provider firebase_provider --force
Make theme
Making a new theme
You can make themes by running the below in the terminal.
dart run nylo_framework:main make:theme bright_theme
# or with the alias metro
metro make:theme bright_theme
This will create a new theme in lib/app/themes
.
Forcefully make a theme
Arguments:
Using the --force
or -f
flag will overwrite an existing theme if it already exists.
dart run nylo_framework:main make:theme bright_theme --force
# or with the alias metro
metro make:theme bright_theme --force
Make Forms
Making a new form
You can make a new form by running the below in the terminal.
dart run nylo_framework:main make:form car_advert_form
# or with the alias metro
metro make:form car_advert_form
This will create a new form in lib/app/forms
.
Forcefully make a form
Arguments:
Using the --force
or -f
flag will overwrite an existing form if it already exists.
dart run nylo_framework:main make:form login_form --force
# or with the alias metro
metro make:form login_form --force
Make Route Guard
Making a new route guard
You can make a route guard by running the below in the terminal.
dart run nylo_framework:main make:route_guard premium_content
# or with the alias metro
metro make:route_guard premium_content
This will create a new route guard in lib/app/route_guards
.
Forcefully make a route guard
Arguments:
Using the --force
or -f
flag will overwrite an existing route guard if it already exists.
dart run nylo_framework:main make:route_guard premium_content --force
# or with the alias metro
metro make:route_guard premium_content --force
Make Config File
Making a new config file
You can make a new config file by running the below in the terminal.
dart run nylo_framework:main make:config shopping_settings
# or with the alias metro
metro make:config shopping_settings
This will create a new config file in lib/app/config
.
Forcefully make a config file
Arguments:
Using the --force
or -f
flag will overwrite an existing config file if it already exists.
dart run nylo_framework:main make:config app_config --force
# or with the alias metro
metro make:config app_config --force
Build App Icons
You can generate all the app icons for IOS and Android by running the below command.
dart run flutter_launcher_icons:main
This uses the flutter_icons configuration in your pubspec.yaml
file.
Publishing files with Slate
If you are new to Slate packages, you can read more about them here.
Publishing files with Slate
You can publish files with Slate by running the below in the terminal.
dart run nylo_framework:main slate:publish my_slate_package
# or with the alias metro
metro slate:publish my_slate_package
This will publish all the files in your project.
Forcefully publish files with Slate
Arguments:
Using the --force
or -f
flag will overwrite any existing files in your project.
dart run nylo_framework:main slate:publish --force
# or with the alias metro
metro slate:publish --force
Install and publish with Slate
You can install and publish files with Slate by running the below in the terminal.
dart run nylo_framework:main slate:install my_slate_package
# or with the alias metro
metro slate:install my_slate_package
This will install the Slate package and publish all the files in your project.
Custom Commands
Custom commands allow you to extend Nylo's CLI with your own project-specific commands. This feature enables you to automate repetitive tasks, implement deployment workflows, or add any custom functionality directly into your project's command-line tools.
- Creating custom commands
- Running Custom Commands
- Adding options to commands
- Adding flags to commands
- Helper methods
Note: You currently cannot import nylo_framework.dart in your custom commands, please use ny_cli.dart instead.
Creating Custom Commands
To create a new custom command, you can use the make:command
feature:
# Using Metro
metro make:command current_time
# Using Dart directly
dart run nylo_framework:main make:command current_time
You can specify a category for your command using the --category
option:
# Specify a category
metro make:command current_time --category="project"
This will create a new command file at lib/app/commands/current_time.dart
with the following structure:
import 'package:nylo_framework/metro/ny_cli.dart';
void main(arguments) => _CurrentTimeCommand(arguments).run();
/// Current Time Command
///
/// Usage:
/// [From Terminal] dart run nylo_framework:main app:current_time
/// [With Metro] metro app:current_time
class _CurrentTimeCommand extends NyCustomCommand {
_CurrentTimeCommand(super.arguments);
@override
CommandBuilder builder(CommandBuilder command) {
command.addOption('format', defaultValue: 'HH:mm:ss');
return command;
}
@override
Future<void> handle(CommandResult result) async {
final format = result.getString("format");
// Get the current time
final now = DateTime.now();
final DateFormat dateFormat = DateFormat(format);
// Format the current time
final formattedTime = dateFormat.format(now);
info("The current time is " + formattedTime);
}
}
The command will automatically be registered in the lib/app/commands/custom_commands.json
file, which contains a list of all registered commands:
[
{
"name": "install_firebase",
"category": "project",
"script": "install_firebase.dart"
},
{
"name": "current_time",
"category": "app",
"script": "current_time.dart"
}
]
Running Custom Commands
Once created, you can run your custom command using either the Metro shorthand or the full Dart command:
# Using Metro
metro app:current_time
# Using Dart directly
dart run nylo_framework:main app:current_time
When you run metro
or dart run nylo_framework:main
without arguments, you'll see your custom commands listed in the menu under the "Custom Commands" section:
[Custom Commands]
app:app_icon
app:clear_pub
project:install_firebase
project:deploy
To display help information for your command, use the --help
or -h
flag:
metro project:install_firebase --help
Adding Options to Commands
Options allow your command to accept additional input from users. You can add options to your command in the builder
method:
@override
CommandBuilder builder(CommandBuilder command) {
// Add an option with a default value
command.addOption(
'environment', // option name
abbr: 'e', // short form abbreviation
help: 'Target deployment environment', // help text
defaultValue: 'development', // default value
allowed: ['development', 'staging', 'production'] // allowed values
);
return command;
}
Then access the option value in your command's handle
method:
@override
Future<void> handle(CommandResult result) async {
final environment = result.getString('environment');
info('Deploying to $environment environment...');
// Command implementation...
}
Example usage:
metro project:deploy --environment=production
# or using abbreviation
metro project:deploy -e production
Adding Flags to Commands
Flags are boolean options that can be toggled on or off. Add flags to your command using the addFlag
method:
@override
CommandBuilder builder(CommandBuilder command) {
command.addFlag(
'verbose', // flag name
abbr: 'v', // short form abbreviation
help: 'Enable verbose output', // help text
defaultValue: false // default to off
);
return command;
}
Then check the flag state in your command's handle
method:
@override
Future<void> handle(CommandResult result) async {
final verbose = result.getBool('verbose');
if (verbose) {
info('Verbose mode enabled');
// Additional logging...
}
// Command implementation...
}
Example usage:
metro project:deploy --verbose
# or using abbreviation
metro project:deploy -v
Helper Methods
The NyCustomCommand
base class provides several helper methods to assist with common tasks:
Printing Messages
Here are some methods for printing messages in different colors:
info |
Print an info message in blue text |
error |
Print an error message in red text |
success |
Print a success message in green text |
warning |
Print a warning message in yellow text |
Running Processes
Run processes and display their output in the console:
addPackage |
Add a package to pubspec.yaml |
addPackages |
Add multiple packages to pubspec.yaml |
runProcess |
Run an external process and display output in the console |
prompt |
Collect user input as text |
confirm |
Ask a yes/no question and return a boolean result |
select |
Present a list of options and let the user select one |
multiSelect |
Allow the user to select multiple options from a list |
Network Requests
Making network requests via the console:
api |
Make an API call using the Nylo API client |
Loading Spinner
Display a loading spinner while executing a function:
withSpinner |
Show a loading spinner while executing a function |
createSpinner |
Create a spinner instance for manual control |
Custom Command Helpers
You can also use the following helper methods to manage command arguments:
getString |
Get a string value from command arguments |
getBool |
Get a boolean value from command arguments |
getInt |
Get an integer value from command arguments |
sleep |
Pause execution for a specified duration |
Running External Processes
// Run a process with output displayed in the console
await runProcess('flutter build web --release');
// Run a process silently
await runProcess('flutter pub get', silent: true);
// Run a process in a specific directory
await runProcess('git pull', workingDirectory: './my-project');
Package Management
// Add a package to pubspec.yaml
addPackage('firebase_core', version: '^2.4.0');
// Add a dev package to pubspec.yaml
addPackage('build_runner', dev: true);
// Add multiple packages at once
addPackages(['firebase_auth', 'firebase_storage', 'quickalert']);
Output Formatting
// Print status messages with color coding
info('Processing files...'); // Blue text
error('Operation failed'); // Red text
success('Deployment complete'); // Green text
warning('Outdated package'); // Yellow text
Interactive Input Methods
The NyCustomCommand
base class provides several methods for collecting user input in the terminal. These methods make it easy to create interactive command-line interfaces for your custom commands.
Text Input
String prompt(String question, {String defaultValue = ''})
Displays a question to the user and collects their text response.
Parameters:
-
question
: The question or prompt to display -
defaultValue
: Optional default value if the user just presses Enter
Returns: The user's input as a string, or the default value if no input was provided
Example:
final name = prompt('What is your project name?', defaultValue: 'my_app');
final description = prompt('Enter a project description:');
Confirmation
bool confirm(String question, {bool defaultValue = false})
Asks the user a yes/no question and returns a boolean result.
Parameters:
-
question
: The yes/no question to ask -
defaultValue
: The default answer (true for yes, false for no)
Returns: true
if the user answered yes, false
if they answered no
Example:
if (confirm('Would you like to continue?', defaultValue: true)) {
// User confirmed or pressed Enter (accepting the default)
await runProcess('flutter pub get');
} else {
// User declined
info('Operation canceled');
}
Single Selection
String select(String question, List<String> options, {String? defaultOption})
Presents a list of options and lets the user select one.
Parameters:
-
question
: The selection prompt -
options
: List of available options -
defaultOption
: Optional default selection
Returns: The selected option as a string
Example:
final environment = select(
'Select deployment environment:',
['development', 'staging', 'production'],
defaultOption: 'development'
);
info('Deploying to $environment environment...');
Multiple Selection
List<String> multiSelect(String question, List<String> options)
Allows the user to select multiple options from a list.
Parameters:
-
question
: The selection prompt -
options
: List of available options
Returns: A list of the selected options
Example:
final packages = multiSelect(
'Select packages to install:',
['firebase_auth', 'dio', 'provider', 'shared_preferences', 'path_provider']
);
if (packages.isNotEmpty) {
info('Installing ${packages.length} packages...');
addPackages(packages);
await runProcess('flutter pub get');
}
API Helper Method
The api
helper method simplifies making network requests from your custom commands.
Future<T?> api<T>(Future<T?> Function(ApiService) request) async
Basic Usage Examples
GET Request
// Fetch data
final userData = await api((request) =>
request.get('https://api.example.com/users/1')
);
POST Request
// Create a resource
final result = await api((request) =>
request.post(
'https://api.example.com/items',
data: {'name': 'New Item', 'price': 19.99}
)
);
PUT Request
// Update a resource
final updateResult = await api((request) =>
request.put(
'https://api.example.com/items/42',
data: {'name': 'Updated Item', 'price': 29.99}
)
);
DELETE Request
// Delete a resource
final deleteResult = await api((request) => request.delete('https://api.example.com/items/42'));
PATCH Request
// Partially update a resource
final patchResult = await api((request) => request.patch(
'https://api.example.com/items/42',
data: {'price': 24.99}
)
);
With Query Parameters
// Add query parameters
final searchResults = await api((request) => request.get(
'https://api.example.com/search',
queryParameters: {'q': 'keyword', 'limit': 10}
)
);
With Spinner
// Using with spinner for better UI
final data = await withSpinner(
task: () async {
final data = await api((request) => request.get('https://api.example.com/config'));
// Process the data
},
message: 'Loading configuration',
);
Spinner Functionality
Spinners provide visual feedback during long-running operations in your custom commands. They display an animated indicator along with a message while your command executes asynchronous tasks, enhancing the user experience by showing progress and status.
Using with spinner
The withSpinner
method lets you wrap an asynchronous task with a spinner animation that automatically starts when the task begins and stops when it completes or fails:
Future<T> withSpinner<T>({
required Future<T> Function() task,
required String message,
String? successMessage,
String? errorMessage,
}) async
Parameters:
-
task
: The asynchronous function to execute -
message
: Text to display while the spinner is running -
successMessage
: Optional message to display upon successful completion -
errorMessage
: Optional message to display if the task fails
Returns: The result of the task function
Example:
@override
Future<void> handle(CommandResult result) async {
// Run a task with a spinner
final projectFiles = await withSpinner(
task: () async {
// Long-running task (e.g., analyzing project files)
await sleep(2);
return ['pubspec.yaml', 'lib/main.dart', 'README.md'];
},
message: 'Analyzing project structure',
successMessage: 'Project analysis complete',
errorMessage: 'Failed to analyze project',
);
// Continue with the results
info('Found ${projectFiles.length} key files');
}
Manual Spinner Control
For more complex scenarios where you need to control the spinner state manually, you can create a spinner instance:
ConsoleSpinner createSpinner(String message)
Parameters:
-
message
: Text to display while the spinner is running
Returns: A ConsoleSpinner
instance that you can manually control
Example with manual control:
@override
Future<void> handle(CommandResult result) async {
// Create a spinner instance
final spinner = createSpinner('Deploying to production');
spinner.start();
try {
// First task
await runProcess('flutter clean', silent: true);
spinner.update(message: 'Building release version');
// Second task
await runProcess('flutter build web --release', silent: true);
spinner.update(message: 'Uploading to server');
// Third task
await runProcess('./deploy.sh', silent: true);
// Complete successfully
spinner.stop(completionMessage: 'Deployment completed successfully', success: true);
} catch (e) {
// Handle failure
spinner.stop(completionMessage: 'Deployment failed: $e', success: false);
rethrow;
}
}
Examples
Simple Task with Spinner
@override
Future<void> handle(CommandResult result) async {
await withSpinner(
task: () async {
// Install dependencies
await runProcess('flutter pub get', silent: true);
return true;
},
message: 'Installing dependencies',
successMessage: 'Dependencies installed successfully',
);
}
Multiple Consecutive Operations
@override
Future<void> handle(CommandResult result) async {
// First operation with spinner
await withSpinner(
task: () => runProcess('flutter clean', silent: true),
message: 'Cleaning project',
);
// Second operation with spinner
await withSpinner(
task: () => runProcess('flutter pub get', silent: true),
message: 'Updating dependencies',
);
// Third operation with spinner
final buildSuccess = await withSpinner(
task: () async {
await runProcess('flutter build apk --release', silent: true);
return true;
},
message: 'Building release APK',
successMessage: 'Release APK built successfully',
);
if (buildSuccess) {
success('Build process completed');
}
}
Complex Workflow with Manual Control
@override
Future<void> handle(CommandResult result) async {
final spinner = createSpinner('Starting deployment process');
spinner.start();
try {
// Run multiple steps with status updates
for (var i = 1; i <= 5; i++) {
spinner.update(message: 'Deployment step $i of 5');
// Simulate work for each step
await sleep(1);
// Pretend step 3 has a warning we want to show
if (i == 3) {
// Temporarily pause the spinner to show a warning
spinner.pause();
warning('Configuration file missing, using defaults');
// Resume the spinner
spinner.resume();
}
}
// Complete the process
spinner.stop(completionMessage: 'Deployment completed successfully', success: true);
} catch (e) {
spinner.stop(completionMessage: 'Deployment failed', success: false);
error('Error details: $e');
}
}
Using spinners in your custom commands provides clear visual feedback to users during long-running operations, creating a more polished and professional command-line experience.
Get a string value from options
String getString(String name, {String defaultValue = ''})
Parameters:
-
name
: The name of the option to retrieve -
defaultValue
: Optional default value if the option is not provided
Returns: The value of the option as a string
Example:
@override
CommandBuilder builder(CommandBuilder command) {
command.addOption("name", defaultValue: "Anthony");
return command;
}
Future<void> handle(CommandResult result) async {
final name = result.getString('name');
info('Hello, $name!');
}
Get a bool value from options
bool getBool(String name, {bool defaultValue = false})
Parameters:
-
name
: The name of the option to retrieve -
defaultValue
: Optional default value if the option is not provided
Returns: The value of the option as a boolean
Example:
@override
CommandBuilder builder(CommandBuilder command) {
command.addFlag("verbose", defaultValue: false);
return command;
}
Future<void> handle(CommandResult result) async {
final verbose = result.getBool('verbose');
if (verbose) {
info('Verbose mode enabled');
} else {
info('Verbose mode disabled');
}
}
Get an int value from options
int getInt(String name, {int defaultValue = 0})
Parameters:
-
name
: The name of the option to retrieve -
defaultValue
: Optional default value if the option is not provided
Returns: The value of the option as an integer
Example:
@override
CommandBuilder builder(CommandBuilder command) {
command.addOption("count", defaultValue: 5);
return command;
}
Future<void> handle(CommandResult result) async {
final count = result.getInt('count');
info('Count is set to $count');
}
Sleep for a specified duration
void sleep(int seconds)
Parameters:
-
seconds
: The number of seconds to sleep
Returns: None
Example:
@override
Future<void> handle(CommandResult result) async {
info('Sleeping for 5 seconds...');
sleep(5);
info('Awake now!');
}