Basics

Strumento CLI Metro

Introduzione

Metro e' uno strumento CLI che funziona sotto il cofano del framework Nylo Website. Fornisce molti strumenti utili per velocizzare lo sviluppo.

Installazione

Quando crei un nuovo progetto Nylo utilizzando nylo init, il comando metro viene configurato automaticamente per il tuo terminale. Puoi iniziare a usarlo immediatamente in qualsiasi progetto Nylo.

Esegui metro dalla directory del tuo progetto per vedere tutti i comandi disponibili:

metro

Dovresti vedere un output simile al seguente.

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

Make controller

Creare un nuovo controller

Puoi creare un nuovo controller eseguendo il seguente comando nel terminale.

metro make:controller profile_controller

Questo creera' un nuovo controller se non esiste gia' nella directory lib/app/controllers/.

Forzare la creazione di un controller

Argomenti:

Usando il flag --force o -f sovrascrivera' un controller esistente se gia' presente.

metro make:controller profile_controller --force

Make model

Creare un nuovo modello

Puoi creare un nuovo modello eseguendo il seguente comando nel terminale.

metro make:model product

Il modello appena creato verra' posizionato in lib/app/models/.

Creare un modello da JSON

Argomenti:

Usando il flag --json o -j creera' un nuovo modello da un payload JSON.

metro make:model product --json

Poi puoi incollare il tuo JSON nel terminale e verra' generato un modello per te.

Forzare la creazione di un modello

Argomenti:

Usando il flag --force o -f sovrascrivera' un modello esistente se gia' presente.

metro make:model product --force

Make page

Creare una nuova pagina

Puoi creare una nuova pagina eseguendo il seguente comando nel terminale.

metro make:page product_page

Questo creera' una nuova pagina se non esiste gia' nella directory lib/resources/pages/.

Creare una pagina con un controller

Puoi creare una nuova pagina con un controller eseguendo il seguente comando nel terminale.

Argomenti:

Usando il flag --controller o -c creera' una nuova pagina con un controller.

metro make:page product_page -c

Creare una pagina auth

Puoi creare una nuova pagina auth eseguendo il seguente comando nel terminale.

Argomenti:

Usando il flag --auth o -a creera' una nuova pagina auth.

metro make:page login_page -a

Creare una pagina iniziale

Puoi creare una nuova pagina iniziale eseguendo il seguente comando nel terminale.

Argomenti:

Usando il flag --initial o -i creera' una nuova pagina iniziale.

metro make:page home_page -i

Forzare la creazione di una pagina

Argomenti:

Usando il flag --force o -f sovrascrivera' una pagina esistente se gia' presente.

metro make:page product_page --force

Make stateless widget

Creare un nuovo widget stateless

Puoi creare un nuovo widget stateless eseguendo il seguente comando nel terminale.

metro make:stateless_widget product_rating_widget

Il comando precedente creera' un nuovo widget se non esiste gia' nella directory lib/resources/widgets/.

Forzare la creazione di un widget stateless

Argomenti:

Usando il flag --force o -f sovrascrivera' un widget esistente se gia' presente.

metro make:stateless_widget product_rating_widget --force

Make stateful widget

Creare un nuovo widget stateful

Puoi creare un nuovo widget stateful eseguendo il seguente comando nel terminale.

metro make:stateful_widget product_rating_widget

Il comando precedente creera' un nuovo widget se non esiste gia' nella directory lib/resources/widgets/.

Forzare la creazione di un widget stateful

Argomenti:

Usando il flag --force o -f sovrascrivera' un widget esistente se gia' presente.

metro make:stateful_widget product_rating_widget --force

Make journey widget

Creare un nuovo journey widget

Puoi creare un nuovo journey widget eseguendo il seguente comando nel terminale.

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"

Il comando precedente creera' un nuovo widget se non esiste gia' nella directory lib/resources/widgets/.

L'argomento --parent viene utilizzato per specificare il widget genitore a cui il nuovo journey widget verra' aggiunto.

Esempio

metro make:navigation_hub onboarding

Successivamente, aggiungi i nuovi journey widget.

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

Forzare la creazione di un journey widget

Argomenti: Usando il flag --force o -f sovrascrivera' un widget esistente se gia' presente.

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

Make API Service

Creare un nuovo API Service

Puoi creare un nuovo API service eseguendo il seguente comando nel terminale.

metro make:api_service user_api_service

Il servizio API appena creato verra' posizionato in lib/app/networking/.

Creare un nuovo API Service con un modello

Puoi creare un nuovo API service con un modello eseguendo il seguente comando nel terminale.

Argomenti:

Usando l'opzione --model o -m creera' un nuovo API service con un modello.

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

Il servizio API appena creato verra' posizionato in lib/app/networking/.

Forzare la creazione di un API Service

Argomenti:

Usando il flag --force o -f sovrascrivera' un API Service esistente se gia' presente.

metro make:api_service user --force

Make event

Creare un nuovo evento

Puoi creare un nuovo evento eseguendo il seguente comando nel terminale.

metro make:event login_event

Questo creera' un nuovo evento in lib/app/events.

Forzare la creazione di un evento

Argomenti:

Usando il flag --force o -f sovrascrivera' un evento esistente se gia' presente.

metro make:event login_event --force

Make provider

Creare un nuovo provider

Crea nuovi provider nella tua applicazione usando il comando seguente.

metro make:provider firebase_provider

Il provider appena creato verra' posizionato in lib/app/providers/.

Forzare la creazione di un provider

Argomenti:

Usando il flag --force o -f sovrascrivera' un provider esistente se gia' presente.

metro make:provider firebase_provider --force

Make theme

Creare un nuovo tema

Puoi creare temi eseguendo il seguente comando nel terminale.

metro make:theme bright_theme

Questo creera' un nuovo tema in lib/resources/themes/.

Forzare la creazione di un tema

Argomenti:

Usando il flag --force o -f sovrascrivera' un tema esistente se gia' presente.

metro make:theme bright_theme --force

Make Forms

Creare un nuovo form

Puoi creare un nuovo form eseguendo il seguente comando nel terminale.

metro make:form car_advert_form

Questo creera' un nuovo form in lib/app/forms.

Forzare la creazione di un form

Argomenti:

Usando il flag --force o -f sovrascrivera' un form esistente se gia' presente.

metro make:form login_form --force

Make Route Guard

Creare un nuovo route guard

Puoi creare un route guard eseguendo il seguente comando nel terminale.

metro make:route_guard premium_content

Questo creera' un nuovo route guard in lib/app/route_guards.

Forzare la creazione di un route guard

Argomenti:

Usando il flag --force o -f sovrascrivera' un route guard esistente se gia' presente.

metro make:route_guard premium_content --force

Make Config File

Creare un nuovo file di configurazione

Puoi creare un nuovo file di configurazione eseguendo il seguente comando nel terminale.

metro make:config shopping_settings

Questo creera' un nuovo file di configurazione in lib/app/config.

Forzare la creazione di un file di configurazione

Argomenti:

Usando il flag --force o -f sovrascrivera' un file di configurazione esistente se gia' presente.

metro make:config app_config --force

Make Command

Creare un nuovo comando

Puoi creare un nuovo comando eseguendo il seguente comando nel terminale.

metro make:command my_command

Questo creera' un nuovo comando in lib/app/commands.

Forzare la creazione di un comando

Argomenti: Usando il flag --force o -f sovrascrivera' un comando esistente se gia' presente.

metro make:command my_command --force

Make State Managed Widget

Puoi creare un nuovo widget con gestione dello stato eseguendo il seguente comando nel terminale.

metro make:state_managed_widget product_rating_widget

Il comando precedente creera' un nuovo widget in lib/resources/widgets/.

Usando il flag --force o -f sovrascrivera' un widget esistente se gia' presente.

metro make:state_managed_widget product_rating_widget --force

Make Navigation Hub

Puoi creare un nuovo navigation hub eseguendo il seguente comando nel terminale.

metro make:navigation_hub dashboard

Questo creera' un nuovo navigation hub in lib/resources/pages/ e aggiungera' automaticamente la route.

Argomenti:

Flag Abbreviazione Descrizione
--auth -a Creare come pagina auth
--initial -i Creare come pagina iniziale
--force -f Sovrascrivere se esiste
# Create as the initial page
metro make:navigation_hub dashboard --initial

Make Bottom Sheet Modal

Puoi creare un nuovo bottom sheet modal eseguendo il seguente comando nel terminale.

metro make:bottom_sheet_modal payment_options

Questo creera' un nuovo bottom sheet modal in lib/resources/widgets/.

Usando il flag --force o -f sovrascrivera' un modal esistente se gia' presente.

metro make:bottom_sheet_modal payment_options --force

Make Button

Puoi creare un nuovo widget pulsante eseguendo il seguente comando nel terminale.

metro make:button checkout_button

Questo creera' un nuovo widget pulsante in lib/resources/widgets/.

Usando il flag --force o -f sovrascrivera' un pulsante esistente se gia' presente.

metro make:button checkout_button --force

Make Interceptor

Puoi creare un nuovo interceptor di rete eseguendo il seguente comando nel terminale.

metro make:interceptor auth_interceptor

Questo creera' un nuovo interceptor in lib/app/networking/dio/interceptors/.

Usando il flag --force o -f sovrascrivera' un interceptor esistente se gia' presente.

metro make:interceptor auth_interceptor --force

Make Env File

Puoi creare un nuovo file di ambiente eseguendo il seguente comando nel terminale.

metro make:env .env.staging

Questo creera' un nuovo file .env nella root del tuo progetto.

Make Key

Genera una APP_KEY sicura per la crittografia dell'ambiente. Questa viene utilizzata per i file .env crittografati nella v7.

metro make:key

Argomenti:

Flag / Opzione Abbreviazione Descrizione
--force -f Sovrascrivere APP_KEY esistente
--file -e File .env di destinazione (predefinito: .env)
# Generate key and overwrite existing
metro make:key --force

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

Generare Icone dell'App

Puoi generare tutte le icone dell'app per IOS e Android eseguendo il seguente comando.

dart run flutter_launcher_icons:main

Questo utilizza la configurazione flutter_icons nel tuo file pubspec.yaml.

Comandi Personalizzati

I comandi personalizzati ti permettono di estendere la CLI di Nylo con i tuoi comandi specifici per il progetto. Questa funzionalita' ti consente di automatizzare attivita' ripetitive, implementare workflow di deployment o aggiungere qualsiasi funzionalita' personalizzata direttamente negli strumenti da riga di comando del tuo progetto.

Nota: Attualmente non puoi importare nylo_framework.dart nei tuoi comandi personalizzati, usa invece ny_cli.dart.

Creare Comandi Personalizzati

Per creare un nuovo comando personalizzato, puoi usare la funzionalita' make:command:

metro make:command current_time

Puoi specificare una categoria per il tuo comando usando l'opzione --category:

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

Questo creera' un nuovo file di comando in lib/app/commands/current_time.dart con la seguente struttura:

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

Il comando verra' automaticamente registrato nel file lib/app/commands/commands.json, che contiene un elenco di tutti i comandi registrati:

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

Eseguire Comandi Personalizzati

Una volta creato, puoi eseguire il tuo comando personalizzato usando la scorciatoia Metro o il comando Dart completo:

metro app:current_time

Quando esegui metro senza argomenti, vedrai i tuoi comandi personalizzati elencati nel menu sotto la sezione "Custom Commands":

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

Per visualizzare le informazioni di aiuto per il tuo comando, usa il flag --help o -h:

metro project:install_firebase --help

Aggiungere Opzioni ai Comandi

Le opzioni permettono al tuo comando di accettare input aggiuntivi dagli utenti. Puoi aggiungere opzioni al tuo comando nel metodo 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;
}

Poi accedi al valore dell'opzione nel metodo handle del tuo comando:

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

  // Command implementation...
}

Esempio di utilizzo:

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

Aggiungere Flag ai Comandi

I flag sono opzioni booleane che possono essere attivate o disattivate. Aggiungi flag al tuo comando usando il metodo 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;
}

Poi controlla lo stato del flag nel metodo handle del tuo comando:

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

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

  // Command implementation...
}

Esempio di utilizzo:

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

Metodi Helper

La classe base NyCustomCommand fornisce diversi metodi helper per assistere con le attivita' comuni:

Stampa Messaggi

Ecco alcuni metodi per stampare messaggi in diversi colori:

info Stampa un messaggio informativo in testo blu
error Stampa un messaggio di errore in testo rosso
success Stampa un messaggio di successo in testo verde
warning Stampa un messaggio di avviso in testo giallo

Esecuzione Processi

Esegui processi e visualizza il loro output nella console:

addPackage Aggiungere un pacchetto a pubspec.yaml
addPackages Aggiungere piu' pacchetti a pubspec.yaml
runProcess Eseguire un processo esterno e visualizzare l'output nella console
prompt Raccogliere input dell'utente come testo
confirm Fare una domanda si'/no e restituire un risultato booleano
select Presentare un elenco di opzioni e permettere all'utente di selezionarne una
multiSelect Permettere all'utente di selezionare piu' opzioni da un elenco

Richieste di Rete

Effettuare richieste di rete tramite la console:

api Effettuare una chiamata API usando il client API di Nylo

Spinner di Caricamento

Visualizzare uno spinner di caricamento durante l'esecuzione di una funzione:

withSpinner Mostrare uno spinner di caricamento durante l'esecuzione di una funzione
createSpinner Creare un'istanza di spinner per il controllo manuale

Helper per Comandi Personalizzati

Puoi anche usare i seguenti metodi helper per gestire gli argomenti dei comandi:

getString Ottenere un valore stringa dagli argomenti del comando
getBool Ottenere un valore booleano dagli argomenti del comando
getInt Ottenere un valore intero dagli argomenti del comando
sleep Mettere in pausa l'esecuzione per una durata specificata

Esecuzione di Processi Esterni

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

Gestione Pacchetti

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

Formattazione dell'Output

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

Metodi di Input Interattivo

La classe base NyCustomCommand fornisce diversi metodi per raccogliere input dall'utente nel terminale. Questi metodi facilitano la creazione di interfacce a riga di comando interattive per i tuoi comandi personalizzati.

Input di Testo

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

Visualizza una domanda all'utente e raccoglie la sua risposta testuale.

Parametri:

  • question: La domanda o il prompt da visualizzare
  • defaultValue: Valore predefinito opzionale se l'utente preme semplicemente Invio

Restituisce: L'input dell'utente come stringa, o il valore predefinito se non e' stato fornito alcun input

Esempio:

final name = prompt('What is your project name?', defaultValue: 'my_app');
final description = prompt('Enter a project description:');

Conferma

bool confirm(String question, {bool defaultValue = false})

Pone all'utente una domanda si'/no e restituisce un risultato booleano.

Parametri:

  • question: La domanda si'/no da porre
  • defaultValue: La risposta predefinita (true per si', false per no)

Restituisce: true se l'utente ha risposto si', false se ha risposto no

Esempio:

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

Selezione Singola

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

Presenta un elenco di opzioni e permette all'utente di selezionarne una.

Parametri:

  • question: Il prompt di selezione
  • options: Elenco delle opzioni disponibili
  • defaultOption: Selezione predefinita opzionale

Restituisce: L'opzione selezionata come stringa

Esempio:

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

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

Selezione Multipla

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

Permette all'utente di selezionare piu' opzioni da un elenco.

Parametri:

  • question: Il prompt di selezione
  • options: Elenco delle opzioni disponibili

Restituisce: Un elenco delle opzioni selezionate

Esempio:

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

Metodo Helper API

Il metodo helper api semplifica l'esecuzione di richieste di rete dai tuoi comandi personalizzati.

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

Esempi di Utilizzo Base

Richiesta GET

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

Richiesta POST

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

Richiesta PUT

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

Richiesta DELETE

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

Richiesta PATCH

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

Con Parametri Query

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

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

Funzionalita' Spinner

Gli spinner forniscono un feedback visivo durante le operazioni di lunga durata nei tuoi comandi personalizzati. Visualizzano un indicatore animato insieme a un messaggio mentre il tuo comando esegue attivita' asincrone, migliorando l'esperienza utente mostrando progresso e stato.

Utilizzo con spinner

Il metodo withSpinner ti permette di avvolgere un'attivita' asincrona con un'animazione spinner che si avvia automaticamente quando l'attivita' inizia e si ferma quando si completa o fallisce:

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

Parametri:

  • task: La funzione asincrona da eseguire
  • message: Testo da visualizzare mentre lo spinner e' in esecuzione
  • successMessage: Messaggio opzionale da visualizzare al completamento con successo
  • errorMessage: Messaggio opzionale da visualizzare se l'attivita' fallisce

Restituisce: Il risultato della funzione task

Esempio:

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

Controllo Manuale dello Spinner

Per scenari piu' complessi in cui hai bisogno di controllare lo stato dello spinner manualmente, puoi creare un'istanza di spinner:

ConsoleSpinner createSpinner(String message)

Parametri:

  • message: Testo da visualizzare mentre lo spinner e' in esecuzione

Restituisce: Un'istanza ConsoleSpinner che puoi controllare manualmente

Esempio con controllo manuale:

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

Esempi

Attivita' Semplice con 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',
  );
}

Operazioni Consecutive Multiple

@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 Complesso con Controllo Manuale

@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'utilizzo degli spinner nei tuoi comandi personalizzati fornisce un feedback visivo chiaro agli utenti durante le operazioni di lunga durata, creando un'esperienza a riga di comando piu' raffinata e professionale.

Ottenere un valore stringa dalle opzioni

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

Parametri:

  • name: Il nome dell'opzione da recuperare
  • defaultValue: Valore predefinito opzionale se l'opzione non viene fornita

Restituisce: Il valore dell'opzione come stringa

Esempio:

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

Ottenere un valore booleano dalle opzioni

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

Parametri:

  • name: Il nome dell'opzione da recuperare
  • defaultValue: Valore predefinito opzionale se l'opzione non viene fornita

Restituisce: Il valore dell'opzione come booleano

Esempio:

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

Ottenere un valore intero dalle opzioni

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

Parametri:

  • name: Il nome dell'opzione da recuperare
  • defaultValue: Valore predefinito opzionale se l'opzione non viene fornita

Restituisce: Il valore dell'opzione come intero

Esempio:

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

Pausa per una durata specificata

void sleep(int seconds)

Parametri:

  • seconds: Il numero di secondi di pausa

Restituisce: Nessuno

Esempio:

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

Formattazione dell'Output

Oltre ai metodi base info, error, success e warning, NyCustomCommand fornisce helper di output aggiuntivi:

@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
  }
}
Metodo Descrizione
line(String message) Stampa testo semplice senza colore
newLine([int count = 1]) Stampa righe vuote
comment(String message) Stampa testo attenuato/grigio
alert(String message) Stampa un riquadro di avviso prominente
ask(String question, {String defaultValue}) Alias per prompt
promptSecret(String question) Input nascosto per dati sensibili
abort([String? message, int exitCode = 1]) Uscire dal comando con un errore

Helper per il File System

NyCustomCommand include helper per il file system integrati cosi' non devi importare manualmente dart:io per le operazioni comuni.

Lettura e Scrittura File

@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');
}
Metodo Descrizione
fileExists(String path) Restituisce true se il file esiste
directoryExists(String path) Restituisce true se la directory esiste
readFile(String path) Legge il file come stringa (asincrono)
readFileSync(String path) Legge il file come stringa (sincrono)
writeFile(String path, String content) Scrive contenuto nel file (asincrono)
writeFileSync(String path, String content) Scrive contenuto nel file (sincrono)
appendFile(String path, String content) Aggiunge contenuto al file
ensureDirectory(String path) Crea la directory se non esiste
deleteFile(String path) Elimina un file
copyFile(String source, String destination) Copia un file

Helper JSON e YAML

Leggi e scrivi file JSON e YAML con helper integrati.

@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']}');
}
Metodo Descrizione
readJson(String path) Legge un file JSON come Map<String, dynamic>
readJsonArray(String path) Legge un file JSON come List<dynamic>
writeJson(String path, dynamic data, {bool pretty = true}) Scrive dati come JSON
appendToJsonArray(String path, Map item, {String? uniqueKey}) Aggiunge a un file array JSON
readYaml(String path) Legge un file YAML come Map<String, dynamic>

Helper per la Conversione del Case

Converti stringhe tra convenzioni di denominazione senza importare il pacchetto 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
}
Metodo Formato Output Esempio
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

Helper per i Percorsi del Progetto

Getter per le directory standard del progetto Nylo Website. Questi restituiscono percorsi relativi alla root del progetto.

@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');
}
Proprieta' Percorso
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) Risolve un percorso relativo all'interno del progetto

Helper per la Piattaforma

Controlla la piattaforma e accedi alle variabili d'ambiente.

@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');
}
Proprieta' / Metodo Descrizione
isWindows true se in esecuzione su Windows
isMacOS true se in esecuzione su macOS
isLinux true se in esecuzione su Linux
workingDirectory Percorso della directory di lavoro corrente
env(String key, [String defaultValue = '']) Legge una variabile d'ambiente di sistema

Comandi Dart e Flutter

Esegui comandi CLI comuni di Dart e Flutter come metodi helper. Ciascuno restituisce il codice di uscita del processo.

@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
}
Metodo Descrizione
dartFormat(String path) Esegue dart format su un file o directory
dartAnalyze([String? path]) Esegue dart analyze
flutterPubGet() Esegue flutter pub get
flutterClean() Esegue flutter clean
flutterBuild(String target, {List<String> args}) Esegue flutter build <target>
flutterTest([String? path]) Esegue flutter test

Manipolazione File Dart

Helper per la modifica programmatica di file Dart, utili quando si costruiscono strumenti di 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'),
  );
}
Metodo Descrizione
addImport(String filePath, String importStatement) Aggiunge un import al file Dart (salta se gia' presente)
insertBeforeClosingBrace(String filePath, String code) Inserisce codice prima dell'ultima } nel file
fileContains(String filePath, String identifier) Controlla se il file contiene una stringa
fileContainsPattern(String filePath, Pattern pattern) Controlla se il file corrisponde a un pattern

Helper per le Directory

Helper per lavorare con le directory e trovare file.

@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');
}
Metodo Descrizione
listDirectory(String path, {bool recursive = false}) Elenca i contenuti della directory
findFiles(String directory, {String? extension, Pattern? namePattern, bool recursive = true}) Trova file corrispondenti ai criteri
deleteDirectory(String path) Elimina la directory ricorsivamente
copyDirectory(String source, String destination) Copia la directory ricorsivamente

Helper per la Validazione

Helper per la validazione e la pulizia dell'input utente per la generazione di codice.

@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'
}
Metodo Descrizione
isValidDartIdentifier(String name) Valida un nome di identificatore Dart
requireArgument(CommandResult result, {String? message}) Richiede un primo argomento non vuoto o interrompe
cleanClassName(String name, {List<String> removeSuffixes}) Pulisce e converte in PascalCase un nome di classe
cleanFileName(String name, {String extension = '.dart'}) Pulisce e converte in snake_case un nome di file

Scaffolding File

Crea uno o piu' file con contenuto usando il sistema di scaffolding.

File Singolo

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

File Multipli

@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 accetta:

Proprieta' Tipo Descrizione
path String Percorso del file da creare
content String Contenuto del file
successMessage String? Messaggio mostrato in caso di successo

Task Runner

Esegui una serie di attivita' denominate con output di stato automatico.

Task Runner Base

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

Task Runner con 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 accetta:

Proprieta' Tipo Predefinito Descrizione
name String obbligatorio Nome dell'attivita' mostrato nell'output
action Future<void> Function() obbligatorio Funzione asincrona da eseguire
stopOnError bool true Se interrompere le attivita' rimanenti se questa fallisce

Output Tabellare

Visualizza tabelle ASCII formattate nella 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'],
    ],
  );
}

Output:

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

Barra di Avanzamento

Visualizza una barra di avanzamento per operazioni con un numero noto di elementi.

Barra di Avanzamento Manuale

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

Elaborare Elementi con Progresso

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

Progresso Sincrono

@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 fornisce:

Metodo Descrizione
start() Avvia la barra di avanzamento
tick([int amount = 1]) Incrementa il progresso
update(int value) Imposta il progresso a un valore specifico
updateMessage(String newMessage) Cambia il messaggio visualizzato
complete([String? completionMessage]) Completa con messaggio opzionale
stop() Ferma senza completare
current Valore di progresso corrente (getter)
percentage Progresso come percentuale (getter)