Basics

Outil CLI Metro

Introduction

Metro est un outil CLI qui fonctionne sous le capot du framework Nylo Website. Il fournit de nombreux outils utiles pour accelerer le developpement.

Installation

Lorsque vous creez un nouveau projet Nylo avec nylo init, la commande metro est automatiquement configuree pour votre terminal. Vous pouvez l'utiliser immediatement dans n'importe quel projet Nylo.

Executez metro depuis le repertoire de votre projet pour voir toutes les commandes disponibles :

metro

Vous devriez voir une sortie similaire a celle ci-dessous.

Metro - Nylo's Companion to Build Flutter apps by Anthony Gordon

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:journey_widget
  make:bottom_sheet_modal
  make:button
  make:form

[Helper Commands]
  make:model
  make:provider
  make:api_service
  make:controller
  make:event
  make:theme
  make:route_guard
  make:config
  make:interceptor
  make:command
  make:env
  make:key

Creer un controleur

Creer un nouveau controleur

Vous pouvez creer un nouveau controleur en executant la commande suivante dans le terminal.

metro make:controller profile_controller

Cela creera un nouveau controleur s'il n'existe pas deja dans le repertoire lib/app/controllers/.

Forcer la creation d'un controleur

Arguments :

L'utilisation du flag --force ou -f ecrasera un controleur existant s'il existe deja.

metro make:controller profile_controller --force

Creer un modele

Creer un nouveau modele

Vous pouvez creer un nouveau modele en executant la commande suivante dans le terminal.

metro make:model product

Le modele nouvellement cree sera place dans lib/app/models/.

Creer un modele a partir de JSON

Arguments :

L'utilisation du flag --json ou -j creera un nouveau modele a partir d'un payload JSON.

metro make:model product --json

Ensuite, vous pouvez coller votre JSON dans le terminal et il generera un modele pour vous.

Forcer la creation d'un modele

Arguments :

L'utilisation du flag --force ou -f ecrasera un modele existant s'il existe deja.

metro make:model product --force

Creer une page

Creer une nouvelle page

Vous pouvez creer une nouvelle page en executant la commande suivante dans le terminal.

metro make:page product_page

Cela creera une nouvelle page si elle n'existe pas deja dans le repertoire lib/resources/pages/.

Creer une page avec un controleur

Vous pouvez creer une nouvelle page avec un controleur en executant la commande suivante dans le terminal.

Arguments :

L'utilisation du flag --controller ou -c creera une nouvelle page avec un controleur.

metro make:page product_page -c

Creer une page d'authentification

Vous pouvez creer une nouvelle page d'authentification en executant la commande suivante dans le terminal.

Arguments :

L'utilisation du flag --auth ou -a creera une nouvelle page d'authentification.

metro make:page login_page -a

Creer une page initiale

Vous pouvez creer une nouvelle page initiale en executant la commande suivante dans le terminal.

Arguments :

L'utilisation du flag --initial ou -i creera une nouvelle page initiale.

metro make:page home_page -i

Forcer la creation d'une page

Arguments :

L'utilisation du flag --force ou -f ecrasera une page existante si elle existe deja.

metro make:page product_page --force

Creer un widget stateless

Creer un nouveau widget stateless

Vous pouvez creer un nouveau widget stateless en executant la commande suivante dans le terminal.

metro make:stateless_widget product_rating_widget

La commande ci-dessus creera un nouveau widget s'il n'existe pas deja dans le repertoire lib/resources/widgets/.

Forcer la creation d'un widget stateless

Arguments :

L'utilisation du flag --force ou -f ecrasera un widget existant s'il existe deja.

metro make:stateless_widget product_rating_widget --force

Creer un widget stateful

Creer un nouveau widget stateful

Vous pouvez creer un nouveau widget stateful en executant la commande suivante dans le terminal.

metro make:stateful_widget product_rating_widget

La commande ci-dessus creera un nouveau widget s'il n'existe pas deja dans le repertoire lib/resources/widgets/.

Forcer la creation d'un widget stateful

Arguments :

L'utilisation du flag --force ou -f ecrasera un widget existant s'il existe deja.

metro make:stateful_widget product_rating_widget --force

Creer un widget journey

Creer un nouveau widget journey

Vous pouvez creer un nouveau widget journey en executant la commande suivante dans le terminal.

metro make:journey_widget product_journey --parent="[NAVIGATION_HUB]"

# Full example if you have a BaseNavigationHub
metro make:journey_widget welcome,user_dob,user_photos --parent="Base"

La commande ci-dessus creera un nouveau widget s'il n'existe pas deja dans le repertoire lib/resources/widgets/.

L'argument --parent est utilise pour specifier le widget parent auquel le nouveau widget journey sera ajoute.

Exemple

metro make:navigation_hub onboarding

Ensuite, ajoutez les nouveaux widgets journey.

metro make:journey_widget welcome,user_dob,user_photos --parent="onboarding"

Forcer la creation d'un widget journey

Arguments : L'utilisation du flag --force ou -f ecrasera un widget existant s'il existe deja.

metro make:journey_widget product_journey --force --parent="[YOUR_NAVIGATION_HUB]"

Creer un service API

Creer un nouveau service API

Vous pouvez creer un nouveau service API en executant la commande suivante dans le terminal.

metro make:api_service user_api_service

Le service API nouvellement cree sera place dans lib/app/networking/.

Creer un nouveau service API avec un modele

Vous pouvez creer un nouveau service API avec un modele en executant la commande suivante dans le terminal.

Arguments :

L'utilisation de l'option --model ou -m creera un nouveau service API avec un modele.

metro make:api_service user --model="User"

Le service API nouvellement cree sera place dans lib/app/networking/.

Forcer la creation d'un service API

Arguments :

L'utilisation du flag --force ou -f ecrasera un service API existant s'il existe deja.

metro make:api_service user --force

Creer un evenement

Creer un nouvel evenement

Vous pouvez creer un nouvel evenement en executant la commande suivante dans le terminal.

metro make:event login_event

Cela creera un nouvel evenement dans lib/app/events.

Forcer la creation d'un evenement

Arguments :

L'utilisation du flag --force ou -f ecrasera un evenement existant s'il existe deja.

metro make:event login_event --force

Creer un provider

Creer un nouveau provider

Creez de nouveaux providers dans votre application en utilisant la commande ci-dessous.

metro make:provider firebase_provider

Le provider nouvellement cree sera place dans lib/app/providers/.

Forcer la creation d'un provider

Arguments :

L'utilisation du flag --force ou -f ecrasera un provider existant s'il existe deja.

metro make:provider firebase_provider --force

Creer un theme

Creer un nouveau theme

Vous pouvez creer des themes en executant la commande suivante dans le terminal.

metro make:theme bright_theme

Cela creera un nouveau theme dans lib/resources/themes/.

Forcer la creation d'un theme

Arguments :

L'utilisation du flag --force ou -f ecrasera un theme existant s'il existe deja.

metro make:theme bright_theme --force

Creer des formulaires

Creer un nouveau formulaire

Vous pouvez creer un nouveau formulaire en executant la commande suivante dans le terminal.

metro make:form car_advert_form

Cela creera un nouveau formulaire dans lib/app/forms.

Forcer la creation d'un formulaire

Arguments :

L'utilisation du flag --force ou -f ecrasera un formulaire existant s'il existe deja.

metro make:form login_form --force

Creer un garde de route

Creer un nouveau garde de route

Vous pouvez creer un garde de route en executant la commande suivante dans le terminal.

metro make:route_guard premium_content

Cela creera un nouveau garde de route dans lib/app/route_guards.

Forcer la creation d'un garde de route

Arguments :

L'utilisation du flag --force ou -f ecrasera un garde de route existant s'il existe deja.

metro make:route_guard premium_content --force

Creer un fichier de configuration

Creer un nouveau fichier de configuration

Vous pouvez creer un nouveau fichier de configuration en executant la commande suivante dans le terminal.

metro make:config shopping_settings

Cela creera un nouveau fichier de configuration dans lib/app/config.

Forcer la creation d'un fichier de configuration

Arguments :

L'utilisation du flag --force ou -f ecrasera un fichier de configuration existant s'il existe deja.

metro make:config app_config --force

Creer une commande

Creer une nouvelle commande

Vous pouvez creer une nouvelle commande en executant la commande suivante dans le terminal.

metro make:command my_command

Cela creera une nouvelle commande dans lib/app/commands.

Forcer la creation d'une commande

Arguments : L'utilisation du flag --force ou -f ecrasera une commande existante si elle existe deja.

metro make:command my_command --force

Creer un widget a etat gere

Vous pouvez creer un nouveau widget a etat gere en executant la commande suivante dans le terminal.

metro make:state_managed_widget product_rating_widget

La commande ci-dessus creera un nouveau widget dans lib/resources/widgets/.

L'utilisation du flag --force ou -f ecrasera un widget existant s'il existe deja.

metro make:state_managed_widget product_rating_widget --force

Creer un Navigation Hub

Vous pouvez creer un nouveau navigation hub en executant la commande suivante dans le terminal.

metro make:navigation_hub dashboard

Cela creera un nouveau navigation hub dans lib/resources/pages/ et ajoutera automatiquement la route.

Arguments :

Flag Court Description
--auth -a Creer comme page d'authentification
--initial -i Creer comme page initiale
--force -f Ecraser si existant
# Create as the initial page
metro make:navigation_hub dashboard --initial

Creer un modal de bas de page

Vous pouvez creer un nouveau modal de bas de page en executant la commande suivante dans le terminal.

metro make:bottom_sheet_modal payment_options

Cela creera un nouveau modal de bas de page dans lib/resources/widgets/.

L'utilisation du flag --force ou -f ecrasera un modal existant s'il existe deja.

metro make:bottom_sheet_modal payment_options --force

Creer un bouton

Vous pouvez creer un nouveau widget bouton en executant la commande suivante dans le terminal.

metro make:button checkout_button

Cela creera un nouveau widget bouton dans lib/resources/widgets/.

L'utilisation du flag --force ou -f ecrasera un bouton existant s'il existe deja.

metro make:button checkout_button --force

Creer un intercepteur

Vous pouvez creer un nouvel intercepteur reseau en executant la commande suivante dans le terminal.

metro make:interceptor auth_interceptor

Cela creera un nouvel intercepteur dans lib/app/networking/dio/interceptors/.

L'utilisation du flag --force ou -f ecrasera un intercepteur existant s'il existe deja.

metro make:interceptor auth_interceptor --force

Creer un fichier Env

Vous pouvez creer un nouveau fichier d'environnement en executant la commande suivante dans le terminal.

metro make:env .env.staging

Cela creera un nouveau fichier .env a la racine de votre projet.

Generer une cle

Generez un APP_KEY securise pour le chiffrement de l'environnement. Celui-ci est utilise pour les fichiers .env chiffres dans la v7.

metro make:key

Arguments :

Flag / Option Court Description
--force -f Ecraser l'APP_KEY existant
--file -e Fichier .env cible (par defaut : .env)
# Generate key and overwrite existing
metro make:key --force

# Generate key for a specific env file
metro make:key --file=.env.production

Generer les icones d'application

Vous pouvez generer toutes les icones d'application pour iOS et Android en executant la commande ci-dessous.

dart run flutter_launcher_icons:main

Cela utilise la configuration flutter_icons dans votre fichier pubspec.yaml.

Commandes personnalisees

Les commandes personnalisees vous permettent d'etendre la CLI de Nylo avec vos propres commandes specifiques au projet. Cette fonctionnalite vous permet d'automatiser les taches repetitives, d'implementer des workflows de deploiement ou d'ajouter toute fonctionnalite personnalisee directement dans les outils en ligne de commande de votre projet.

Note : Vous ne pouvez actuellement pas importer nylo_framework.dart dans vos commandes personnalisees, veuillez utiliser ny_cli.dart a la place.

Creer des commandes personnalisees

Pour creer une nouvelle commande personnalisee, vous pouvez utiliser la fonctionnalite make:command :

metro make:command current_time

Vous pouvez specifier une categorie pour votre commande en utilisant l'option --category :

# Specify a category
metro make:command current_time --category="project"

Cela creera un nouveau fichier de commande dans lib/app/commands/current_time.dart avec la structure suivante :

import 'package:nylo_framework/metro/ny_cli.dart';

void main(arguments) => _CurrentTimeCommand(arguments).run();

/// Current Time Command
///
/// Usage:
///   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);
  }
}

La commande sera automatiquement enregistree dans le fichier lib/app/commands/commands.json, qui contient une liste de toutes les commandes enregistrees :

[
  {
    "name": "install_firebase",
    "category": "project",
    "script": "install_firebase.dart"
  },
  {
    "name": "current_time",
    "category": "app",
    "script": "current_time.dart"
  }
]

Executer des commandes personnalisees

Une fois creee, vous pouvez executer votre commande personnalisee en utilisant soit le raccourci Metro, soit la commande Dart complete :

metro app:current_time

Lorsque vous executez metro sans arguments, vous verrez vos commandes personnalisees listees dans le menu sous la section "Custom Commands" :

[Custom Commands]
  app:app_icon
  app:clear_pub
  project:install_firebase
  project:deploy

Pour afficher les informations d'aide pour votre commande, utilisez le flag --help ou -h :

metro project:install_firebase --help

Ajouter des options aux commandes

Les options permettent a votre commande d'accepter des entrees supplementaires des utilisateurs. Vous pouvez ajouter des options a votre commande dans la methode builder :

@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;
}

Ensuite, accedez a la valeur de l'option dans la methode handle de votre commande :

@override
Future<void> handle(CommandResult result) async {
  final environment = result.getString('environment');
  info('Deploying to $environment environment...');

  // Command implementation...
}

Exemple d'utilisation :

metro project:deploy --environment=production
# or using abbreviation
metro project:deploy -e production

Ajouter des flags aux commandes

Les flags sont des options booleennes qui peuvent etre activees ou desactivees. Ajoutez des flags a votre commande en utilisant la methode addFlag :

@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;
}

Ensuite, verifiez l'etat du flag dans la methode handle de votre commande :

@override
Future<void> handle(CommandResult result) async {
  final verbose = result.getBool('verbose');

  if (verbose) {
    info('Verbose mode enabled');
    // Additional logging...
  }

  // Command implementation...
}

Exemple d'utilisation :

metro project:deploy --verbose
# or using abbreviation
metro project:deploy -v

Methodes d'aide

La classe de base NyCustomCommand fournit plusieurs methodes d'aide pour les taches courantes :

Affichage de messages

Voici quelques methodes pour afficher des messages dans differentes couleurs :

info Afficher un message d'information en texte bleu
error Afficher un message d'erreur en texte rouge
success Afficher un message de succes en texte vert
warning Afficher un message d'avertissement en texte jaune

Execution de processus

Executer des processus et afficher leur sortie dans la console :

addPackage Ajouter un package a pubspec.yaml
addPackages Ajouter plusieurs packages a pubspec.yaml
runProcess Executer un processus externe et afficher la sortie dans la console
prompt Collecter la saisie utilisateur sous forme de texte
confirm Poser une question oui/non et retourner un resultat booleen
select Presenter une liste d'options et laisser l'utilisateur en choisir une
multiSelect Permettre a l'utilisateur de selectionner plusieurs options dans une liste

Requetes reseau

Effectuer des requetes reseau via la console :

api Effectuer un appel API en utilisant le client API Nylo

Indicateur de chargement

Afficher un indicateur de chargement pendant l'execution d'une fonction :

withSpinner Afficher un indicateur de chargement pendant l'execution d'une fonction
createSpinner Creer une instance de spinner pour un controle manuel

Helpers pour commandes personnalisees

Vous pouvez egalement utiliser les methodes d'aide suivantes pour gerer les arguments de commande :

getString Obtenir une valeur string des arguments de commande
getBool Obtenir une valeur booleenne des arguments de commande
getInt Obtenir une valeur entiere des arguments de commande
sleep Mettre en pause l'execution pour une duree specifiee

Execution de processus externes

// 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');

Gestion des packages

// 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']);

Formatage de sortie

// 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

Methodes de saisie interactive

La classe de base NyCustomCommand fournit plusieurs methodes pour collecter les saisies utilisateur dans le terminal. Ces methodes facilitent la creation d'interfaces en ligne de commande interactives pour vos commandes personnalisees.

Saisie de texte

String prompt(String question, {String defaultValue = ''})

Affiche une question a l'utilisateur et collecte sa reponse textuelle.

Parametres :

  • question : La question ou l'invite a afficher
  • defaultValue : Valeur par defaut optionnelle si l'utilisateur appuie simplement sur Entree

Retourne : La saisie de l'utilisateur sous forme de string, ou la valeur par defaut si aucune saisie n'a ete fournie

Exemple :

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})

Pose une question oui/non a l'utilisateur et retourne un resultat booleen.

Parametres :

  • question : La question oui/non a poser
  • defaultValue : La reponse par defaut (true pour oui, false pour non)

Retourne : true si l'utilisateur a repondu oui, false s'il a repondu non

Exemple :

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');
}

Selection unique

String select(String question, List<String> options, {String? defaultOption})

Presente une liste d'options et laisse l'utilisateur en choisir une.

Parametres :

  • question : L'invite de selection
  • options : Liste des options disponibles
  • defaultOption : Selection par defaut optionnelle

Retourne : L'option selectionnee sous forme de string

Exemple :

final environment = select(
  'Select deployment environment:',
  ['development', 'staging', 'production'],
  defaultOption: 'development'
);

info('Deploying to $environment environment...');

Selection multiple

List<String> multiSelect(String question, List<String> options)

Permet a l'utilisateur de selectionner plusieurs options dans une liste.

Parametres :

  • question : L'invite de selection
  • options : Liste des options disponibles

Retourne : Une liste des options selectionnees

Exemple :

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');
}

Methode d'aide API

La methode d'aide api simplifie les requetes reseau depuis vos commandes personnalisees.

Future<T?> api<T>(Future<T?> Function(ApiService) request) async

Exemples d'utilisation de base

Requete GET

// Fetch data
final userData = await api((request) =>
  request.get('https://api.example.com/users/1')
);

Requete POST

// Create a resource
final result = await api((request) =>
  request.post(
    'https://api.example.com/items',
    data: {'name': 'New Item', 'price': 19.99}
  )
);

Requete PUT

// Update a resource
final updateResult = await api((request) =>
  request.put(
    'https://api.example.com/items/42',
    data: {'name': 'Updated Item', 'price': 29.99}
  )
);

Requete DELETE

// Delete a resource
final deleteResult = await api((request) => request.delete('https://api.example.com/items/42'));

Requete PATCH

// Partially update a resource
final patchResult = await api((request) => request.patch(
    'https://api.example.com/items/42',
    data: {'price': 24.99}
  )
);

Avec des parametres de requete

// Add query parameters
final searchResults = await api((request) => request.get(
    'https://api.example.com/search',
    queryParameters: {'q': 'keyword', 'limit': 10}
  )
);

Avec 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',
);

Fonctionnalite Spinner

Les spinners fournissent un retour visuel pendant les operations longues dans vos commandes personnalisees. Ils affichent un indicateur anime accompagne d'un message pendant que votre commande execute des taches asynchrones, ameliorant l'experience utilisateur en montrant la progression et le statut.

Utiliser avec spinner

La methode withSpinner vous permet d'envelopper une tache asynchrone avec une animation de spinner qui demarre automatiquement lorsque la tache commence et s'arrete lorsqu'elle se termine ou echoue :

Future<T> withSpinner<T>({
  required Future<T> Function() task,
  required String message,
  String? successMessage,
  String? errorMessage,
}) async

Parametres :

  • task : La fonction asynchrone a executer
  • message : Texte a afficher pendant que le spinner tourne
  • successMessage : Message optionnel a afficher en cas de succes
  • errorMessage : Message optionnel a afficher en cas d'echec

Retourne : Le resultat de la fonction de tache

Exemple :

@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');
}

Controle manuel du spinner

Pour des scenarios plus complexes ou vous devez controler manuellement l'etat du spinner, vous pouvez creer une instance de spinner :

ConsoleSpinner createSpinner(String message)

Parametres :

  • message : Texte a afficher pendant que le spinner tourne

Retourne : Une instance ConsoleSpinner que vous pouvez controler manuellement

Exemple avec controle manuel :

@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('Building release version');

    // Second task
    await runProcess('flutter build web --release', silent: true);
    spinner.update('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;
  }
}

Exemples

Tache simple avec 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',
  );
}

Plusieurs operations consecutives

@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');
  }
}

Workflow complexe avec controle manuel

@override
Future<void> handle(CommandResult result) async {
  final spinner = createSpinner('Starting deployment process');
  spinner.start();

  try {
    // Run multiple steps with status updates
    spinner.update('Step 1: Cleaning project');
    await runProcess('flutter clean', silent: true);

    spinner.update('Step 2: Fetching dependencies');
    await runProcess('flutter pub get', silent: true);

    spinner.update('Step 3: Building release');
    await runProcess('flutter build web --release', silent: true);

    // Complete the process
    spinner.stop(completionMessage: 'Deployment completed successfully', success: true);

  } catch (e) {
    spinner.stop(completionMessage: 'Deployment failed: $e', success: false);
  }
}

L'utilisation de spinners dans vos commandes personnalisees fournit un retour visuel clair aux utilisateurs pendant les operations longues, creant une experience en ligne de commande plus polie et professionnelle.

Obtenir une valeur string des options

String getString(String name, {String defaultValue = ''})

Parametres :

  • name : Le nom de l'option a recuperer
  • defaultValue : Valeur par defaut optionnelle si l'option n'est pas fournie

Retourne : La valeur de l'option sous forme de string

Exemple :

@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!');
}

Obtenir une valeur booleenne des options

bool getBool(String name, {bool defaultValue = false})

Parametres :

  • name : Le nom de l'option a recuperer
  • defaultValue : Valeur par defaut optionnelle si l'option n'est pas fournie

Retourne : La valeur de l'option sous forme de booleen

Exemple :

@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');
  }
}

Obtenir une valeur entiere des options

int getInt(String name, {int defaultValue = 0})

Parametres :

  • name : Le nom de l'option a recuperer
  • defaultValue : Valeur par defaut optionnelle si l'option n'est pas fournie

Retourne : La valeur de l'option sous forme d'entier

Exemple :

@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');
}

Mettre en pause pour une duree specifiee

void sleep(int seconds)

Parametres :

  • seconds : Le nombre de secondes de pause

Retourne : Rien

Exemple :

@override
Future<void> handle(CommandResult result) async {
  info('Sleeping for 5 seconds...');
  await sleep(5);
  info('Awake now!');
}

Formatage de sortie

Au-dela des methodes basiques info, error, success et warning, NyCustomCommand fournit des helpers de sortie supplementaires :

@override
Future<void> handle(CommandResult result) async {
  // Print plain text (no color)
  line('Processing your request...');

  // Print blank lines
  newLine();       // one blank line
  newLine(3);      // three blank lines

  // Print a muted comment (gray text)
  comment('This is a background note');

  // Print a prominent alert box
  alert('Important: Please read carefully');

  // Ask is an alias for prompt
  final name = ask('What is your name?');

  // Hidden input for sensitive data (e.g., passwords, API keys)
  final apiKey = promptSecret('Enter your API key:');

  // Abort the command with an error message and exit code
  if (name.isEmpty) {
    abort('Name is required');  // exits with code 1
  }
}
Methode Description
line(String message) Afficher du texte brut sans couleur
newLine([int count = 1]) Afficher des lignes vides
comment(String message) Afficher du texte attenue/gris
alert(String message) Afficher une boite d'alerte proeminente
ask(String question, {String defaultValue}) Alias pour prompt
promptSecret(String question) Saisie masquee pour les donnees sensibles
abort([String? message, int exitCode = 1]) Quitter la commande avec une erreur

Helpers du systeme de fichiers

NyCustomCommand inclut des helpers de systeme de fichiers integres pour que vous n'ayez pas a importer manuellement dart:io pour les operations courantes.

Lecture et ecriture de fichiers

@override
Future<void> handle(CommandResult result) async {
  // Check if a file exists
  if (fileExists('lib/config/app.dart')) {
    info('Config file found');
  }

  // Check if a directory exists
  if (directoryExists('lib/app/models')) {
    info('Models directory found');
  }

  // Read a file (async)
  String content = await readFile('pubspec.yaml');

  // Read a file (sync)
  String contentSync = readFileSync('pubspec.yaml');

  // Write to a file (async)
  await writeFile('lib/generated/output.dart', 'class Output {}');

  // Write to a file (sync)
  writeFileSync('lib/generated/output.dart', 'class Output {}');

  // Append content to a file
  await appendFile('log.txt', 'New log entry\n');

  // Ensure a directory exists (creates it if missing)
  await ensureDirectory('lib/generated');

  // Delete a file
  await deleteFile('lib/generated/output.dart');

  // Copy a file
  await copyFile('lib/config/app.dart', 'lib/config/app.bak.dart');
}
Methode Description
fileExists(String path) Retourne true si le fichier existe
directoryExists(String path) Retourne true si le repertoire existe
readFile(String path) Lire un fichier comme string (async)
readFileSync(String path) Lire un fichier comme string (sync)
writeFile(String path, String content) Ecrire du contenu dans un fichier (async)
writeFileSync(String path, String content) Ecrire du contenu dans un fichier (sync)
appendFile(String path, String content) Ajouter du contenu a un fichier
ensureDirectory(String path) Creer le repertoire s'il n'existe pas
deleteFile(String path) Supprimer un fichier
copyFile(String source, String destination) Copier un fichier

Helpers JSON et YAML

Lire et ecrire des fichiers JSON et YAML avec les helpers integres.

@override
Future<void> handle(CommandResult result) async {
  // Read a JSON file as a Map
  Map<String, dynamic> config = await readJson('config.json');

  // Read a JSON file as a List
  List<dynamic> items = await readJsonArray('lib/app/commands/commands.json');

  // Write data to a JSON file (pretty printed by default)
  await writeJson('output.json', {'name': 'MyApp', 'version': '1.0.0'});

  // Write compact JSON
  await writeJson('output.json', data, pretty: false);

  // Append an item to a JSON array file
  // If the file contains [{"name": "a"}], this adds to that array
  await appendToJsonArray(
    'lib/app/commands/commands.json',
    {'name': 'my_command', 'category': 'app', 'script': 'my_command.dart'},
    uniqueKey: 'name',  // prevents duplicates by this key
  );

  // Read a YAML file as a Map
  Map<String, dynamic> pubspec = await readYaml('pubspec.yaml');
  info('Project: ${pubspec['name']}');
}
Methode Description
readJson(String path) Lire un fichier JSON comme Map<String, dynamic>
readJsonArray(String path) Lire un fichier JSON comme List<dynamic>
writeJson(String path, dynamic data, {bool pretty = true}) Ecrire des donnees en JSON
appendToJsonArray(String path, Map item, {String? uniqueKey}) Ajouter a un fichier tableau JSON
readYaml(String path) Lire un fichier YAML comme Map<String, dynamic>

Helpers de conversion de casse

Convertir des strings entre les conventions de nommage sans importer le package recase.

@override
Future<void> handle(CommandResult result) async {
  String input = 'user profile page';

  info(snakeCase(input));    // user_profile_page
  info(camelCase(input));    // userProfilePage
  info(pascalCase(input));   // UserProfilePage
  info(titleCase(input));    // User Profile Page
  info(kebabCase(input));    // user-profile-page
  info(constantCase(input)); // USER_PROFILE_PAGE
}
Methode Format de sortie Exemple
snakeCase(String input) snake_case user_profile
camelCase(String input) camelCase userProfile
pascalCase(String input) PascalCase UserProfile
titleCase(String input) Title Case User Profile
kebabCase(String input) kebab-case user-profile
constantCase(String input) CONSTANT_CASE USER_PROFILE

Helpers de chemins projet

Getters pour les repertoires de projet Nylo Website standard. Ceux-ci retournent des chemins relatifs a la racine du projet.

@override
Future<void> handle(CommandResult result) async {
  info(modelsPath);       // lib/app/models
  info(controllersPath);  // lib/app/controllers
  info(widgetsPath);      // lib/resources/widgets
  info(pagesPath);        // lib/resources/pages
  info(commandsPath);     // lib/app/commands
  info(configPath);       // lib/config
  info(providersPath);    // lib/app/providers
  info(eventsPath);       // lib/app/events
  info(networkingPath);   // lib/app/networking
  info(themesPath);       // lib/resources/themes

  // Build a custom path relative to the project root
  String customPath = projectPath('lib/app/services/auth_service.dart');
}
Propriete Chemin
modelsPath lib/app/models
controllersPath lib/app/controllers
widgetsPath lib/resources/widgets
pagesPath lib/resources/pages
commandsPath lib/app/commands
configPath lib/config
providersPath lib/app/providers
eventsPath lib/app/events
networkingPath lib/app/networking
themesPath lib/resources/themes
projectPath(String relativePath) Resoudre un chemin relatif dans le projet

Helpers de plateforme

Verifier la plateforme et acceder aux variables d'environnement.

@override
Future<void> handle(CommandResult result) async {
  // Platform checks
  if (isWindows) {
    info('Running on Windows');
  } else if (isMacOS) {
    info('Running on macOS');
  } else if (isLinux) {
    info('Running on Linux');
  }

  // Current working directory
  info('Working in: $workingDirectory');

  // Read system environment variables
  String home = env('HOME', '/default/path');
}
Propriete / Methode Description
isWindows true si execute sur Windows
isMacOS true si execute sur macOS
isLinux true si execute sur Linux
workingDirectory Chemin du repertoire de travail actuel
env(String key, [String defaultValue = '']) Lire une variable d'environnement systeme

Commandes Dart et Flutter

Executer les commandes CLI courantes Dart et Flutter comme methodes d'aide. Chacune retourne le code de sortie du processus.

@override
Future<void> handle(CommandResult result) async {
  // Format a Dart file or directory
  await dartFormat('lib/app/models/user.dart');

  // Run dart analyze
  int analyzeResult = await dartAnalyze('lib/');

  // Run flutter pub get
  await flutterPubGet();

  // Run flutter clean
  await flutterClean();

  // Build for a target with additional args
  await flutterBuild('apk', args: ['--release', '--split-per-abi']);
  await flutterBuild('web', args: ['--release']);

  // Run flutter test
  await flutterTest();
  await flutterTest('test/unit/');  // specific directory
}
Methode Description
dartFormat(String path) Executer dart format sur un fichier ou repertoire
dartAnalyze([String? path]) Executer dart analyze
flutterPubGet() Executer flutter pub get
flutterClean() Executer flutter clean
flutterBuild(String target, {List<String> args}) Executer flutter build <target>
flutterTest([String? path]) Executer flutter test

Manipulation de fichiers Dart

Helpers pour editer programmatiquement des fichiers Dart, utiles lors de la creation d'outils de scaffolding.

@override
Future<void> handle(CommandResult result) async {
  // Add an import statement to a Dart file (avoids duplicates)
  await addImport(
    'lib/bootstrap/providers.dart',
    "import '/app/providers/firebase_provider.dart';",
  );

  // Insert code before the last closing brace in a file
  // Useful for adding entries to registration maps
  await insertBeforeClosingBrace(
    'lib/bootstrap/providers.dart',
    '  FirebaseProvider(),',
  );

  // Check if a file contains a specific string
  bool hasImport = await fileContains(
    'lib/bootstrap/providers.dart',
    'firebase_provider',
  );

  // Check if a file matches a regex pattern
  bool hasClass = await fileContainsPattern(
    'lib/app/models/user.dart',
    RegExp(r'class User'),
  );
}
Methode Description
addImport(String filePath, String importStatement) Ajouter un import a un fichier Dart (ignore si deja present)
insertBeforeClosingBrace(String filePath, String code) Inserer du code avant la derniere } dans un fichier
fileContains(String filePath, String identifier) Verifier si un fichier contient une string
fileContainsPattern(String filePath, Pattern pattern) Verifier si un fichier correspond a un pattern

Helpers de repertoires

Helpers pour travailler avec des repertoires et trouver des fichiers.

@override
Future<void> handle(CommandResult result) async {
  // List directory contents
  var entities = listDirectory('lib/app/models');
  for (var entity in entities) {
    info(entity.path);
  }

  // List recursively
  var allEntities = listDirectory('lib/', recursive: true);

  // Find files matching criteria
  List<File> dartFiles = findFiles(
    'lib/app/models',
    extension: '.dart',
    recursive: true,
  );

  // Find files by name pattern
  List<File> testFiles = findFiles(
    'test/',
    namePattern: RegExp(r'_test\.dart$'),
  );

  // Delete a directory recursively
  await deleteDirectory('build/');

  // Copy a directory (recursive)
  await copyDirectory('lib/templates', 'lib/generated');
}
Methode Description
listDirectory(String path, {bool recursive = false}) Lister le contenu d'un repertoire
findFiles(String directory, {String? extension, Pattern? namePattern, bool recursive = true}) Trouver des fichiers correspondant aux criteres
deleteDirectory(String path) Supprimer un repertoire recursivement
copyDirectory(String source, String destination) Copier un repertoire recursivement

Helpers de validation

Helpers pour valider et nettoyer les saisies utilisateur pour la generation de code.

@override
Future<void> handle(CommandResult result) async {
  // Validate a Dart identifier
  if (!isValidDartIdentifier('MyClass')) {
    error('Invalid Dart identifier');
  }

  // Require a non-empty first argument
  String name = requireArgument(result, message: 'Please provide a name');

  // Clean a class name (PascalCase, remove suffixes)
  String className = cleanClassName('user_model', removeSuffixes: ['_model']);
  // Returns: 'User'

  // Clean a file name (snake_case with extension)
  String fileName = cleanFileName('UserModel', extension: '.dart');
  // Returns: 'user_model.dart'
}
Methode Description
isValidDartIdentifier(String name) Valider un nom d'identifiant Dart
requireArgument(CommandResult result, {String? message}) Exiger un premier argument non vide ou abandonner
cleanClassName(String name, {List<String> removeSuffixes}) Nettoyer et mettre en PascalCase un nom de classe
cleanFileName(String name, {String extension = '.dart'}) Nettoyer et mettre en snake_case un nom de fichier

Scaffolding de fichiers

Creer un ou plusieurs fichiers avec du contenu en utilisant le systeme de scaffolding.

Fichier unique

@override
Future<void> handle(CommandResult result) async {
  await scaffold(
    path: 'lib/app/services/auth_service.dart',
    content: '''
class AuthService {
  Future<bool> login(String email, String password) async {
    // TODO: implement login
    return false;
  }
}
''',
    force: false,  // don't overwrite if exists
    successMessage: 'AuthService created',
  );
}

Fichiers multiples

@override
Future<void> handle(CommandResult result) async {
  await scaffoldMany([
    ScaffoldFile(
      path: 'lib/app/models/product.dart',
      content: 'class Product {}',
      successMessage: 'Product model created',
    ),
    ScaffoldFile(
      path: 'lib/app/networking/product_api_service.dart',
      content: 'class ProductApiService {}',
      successMessage: 'Product API service created',
    ),
  ], force: false);
}

La classe ScaffoldFile accepte :

Propriete Type Description
path String Chemin du fichier a creer
content String Contenu du fichier
successMessage String? Message affiche en cas de succes

Executeur de taches

Executer une serie de taches nommees avec une sortie de statut automatique.

Executeur de taches basique

@override
Future<void> handle(CommandResult result) async {
  await runTasks([
    CommandTask(
      'Clean project',
      () => runProcess('flutter clean', silent: true),
    ),
    CommandTask(
      'Fetch dependencies',
      () => runProcess('flutter pub get', silent: true),
    ),
    CommandTask(
      'Run tests',
      () => runProcess('flutter test', silent: true),
      stopOnError: true,  // stop pipeline if this fails (default)
    ),
  ]);
}

Executeur de taches avec Spinner

@override
Future<void> handle(CommandResult result) async {
  await runTasksWithSpinner([
    CommandTask(
      name: 'Preparing release',
      action: () async {
        await flutterClean();
        await flutterPubGet();
      },
    ),
    CommandTask(
      name: 'Building APK',
      action: () => flutterBuild('apk', args: ['--release']),
    ),
  ]);
}

La classe CommandTask accepte :

Propriete Type Defaut Description
name String requis Nom de la tache affiche dans la sortie
action Future<void> Function() requis Fonction asynchrone a executer
stopOnError bool true Arreter les taches restantes en cas d'echec

Sortie en tableau

Afficher des tableaux ASCII formates dans la console.

@override
Future<void> handle(CommandResult result) async {
  table(
    ['Name', 'Version', 'Status'],
    [
      ['nylo_framework', '7.0.0', 'installed'],
      ['nylo_support', '7.0.0', 'installed'],
      ['dio', '5.4.0', 'installed'],
    ],
  );
}

Sortie :

┌─────────────────┬─────────┬───────────┐
│ Name            │ Version │ Status    │
├─────────────────┼─────────┼───────────┤
│ nylo_framework  │ 7.0.0   │ installed │
│ nylo_support    │ 7.0.0   │ installed │
│ dio             │ 5.4.0   │ installed │
└─────────────────┴─────────┴───────────┘

Barre de progression

Afficher une barre de progression pour les operations avec un nombre d'elements connu.

Barre de progression manuelle

@override
Future<void> handle(CommandResult result) async {
  // Create a progress bar for 100 items
  final progress = progressBar(100, message: 'Processing files');
  progress.start();

  for (int i = 0; i < 100; i++) {
    await Future.delayed(Duration(milliseconds: 50));
    progress.tick();  // increment by 1
  }

  progress.complete('All files processed');
}

Traiter des elements avec progression

@override
Future<void> handle(CommandResult result) async {
  final files = findFiles('lib/', extension: '.dart');

  // Process items with automatic progress tracking
  final results = await withProgress<File, String>(
    items: files,
    process: (file, index) async {
      // process each file
      return file.path;
    },
    message: 'Analyzing Dart files',
    completionMessage: 'Analysis complete',
  );

  info('Processed ${results.length} files');
}

Progression synchrone

@override
Future<void> handle(CommandResult result) async {
  final items = ['a', 'b', 'c', 'd', 'e'];

  final results = withProgressSync<String, String>(
    items: items,
    process: (item, index) {
      // synchronous processing
      return item.toUpperCase();
    },
    message: 'Converting items',
  );

  info('Results: $results');
}

La classe ConsoleProgressBar fournit :

Methode Description
start() Demarrer la barre de progression
tick([int amount = 1]) Incrementer la progression
update(int value) Definir la progression a une valeur specifique
updateMessage(String newMessage) Changer le message affiche
complete([String? completionMessage]) Terminer avec un message optionnel
stop() Arreter sans terminer
current Valeur de progression actuelle (getter)
percentage Progression en pourcentage (getter)