Advanced

Befehle

Einleitung

Befehle ermöglichen es Ihnen, die CLI von Nylo Website mit benutzerdefinierten, projektspezifischen Werkzeugen zu erweitern. Indem Sie NyCustomCommand ableiten, können Sie wiederkehrende Aufgaben automatisieren, Deployment-Workflows erstellen, Code generieren oder jede beliebige Funktionalität direkt in Ihrem Terminal hinzufügen.

Jeder benutzerdefinierte Befehl hat Zugriff auf eine umfangreiche Sammlung integrierter Helfer für Datei-I/O, JSON/YAML, interaktive Eingabeaufforderungen, Spinner, Fortschrittsbalken, API-Anfragen und mehr -- alles ohne zusätzliche Pakete importieren zu müssen.

Hinweis: Benutzerdefinierte Befehle laufen außerhalb der Flutter-Laufzeitumgebung. Sie können nylo_framework.dart nicht in Ihren Befehlen importieren. Verwenden Sie stattdessen ny_cli.dart.

Befehle erstellen

Erstellen Sie einen neuen Befehl mit Metro oder der Dart-CLI:

metro make:command current_time

Sie können eine Kategorie für Ihren Befehl mit der Option --category angeben:

metro make:command current_time --category="project"

Dies erstellt eine neue Datei unter lib/app/commands/current_time.dart und registriert sie im Befehlsregister.

Befehlsstruktur

Jeder Befehl erweitert NyCustomCommand und implementiert zwei zentrale Methoden:

  • builder() -- Optionen und Flags konfigurieren
  • handle() -- die Befehlslogik ausführen
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");
    info("Current time format: $format");
  }
}

Befehle ausführen

Führen Sie Ihren Befehl mit Metro oder Dart aus:

metro app:current_time

Der Befehlsname folgt dem Muster kategorie:name. Wenn Sie metro ohne Argumente ausführen, erscheinen benutzerdefinierte Befehle im Abschnitt Custom Commands:

[Custom Commands]
  app:current_time
  project:install_firebase
  project:deploy

Um die Hilfe für einen Befehl anzuzeigen:

metro app:current_time --help

Befehlsregister

Alle benutzerdefinierten Befehle werden in lib/app/commands/commands.json registriert. Diese Datei wird automatisch aktualisiert, wenn Sie make:command verwenden:

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

Jeder Eintrag hat:

Feld Beschreibung
name Der Befehlsname (wird nach dem Kategoriepräfix verwendet)
category Die Befehlskategorie (z.B. app, project)
script Die Dart-Datei in lib/app/commands/

Optionen und Flags

Konfigurieren Sie die Optionen und Flags Ihres Befehls in der Methode builder() mit CommandBuilder.

Optionen hinzufügen

Optionen akzeptieren einen Wert vom Benutzer:

@override
CommandBuilder builder(CommandBuilder command) {
  command.addOption(
    'environment',
    abbr: 'e',
    help: 'Target deployment environment',
    defaultValue: 'development',
    allowed: ['development', 'staging', 'production'],
  );
  return command;
}

Verwendung:

metro project:deploy --environment=production
# or using abbreviation
metro project:deploy -e production
Parameter Typ Beschreibung
name String Optionsname
abbr String? Einzeichen-Abkürzung
help String? Hilfetext, der mit --help angezeigt wird
allowed List<String>? Auf erlaubte Werte beschränken
defaultValue String? Standardwert, wenn nicht angegeben

Flags hinzufügen

Flags sind boolesche Schalter:

@override
CommandBuilder builder(CommandBuilder command) {
  command.addFlag(
    'verbose',
    abbr: 'v',
    help: 'Enable verbose output',
    defaultValue: false,
  );
  return command;
}

Verwendung:

metro project:deploy --verbose
metro project:deploy -v
Parameter Typ Beschreibung
name String Flag-Name
abbr String? Einzeichen-Abkürzung
help String? Hilfetext, der mit --help angezeigt wird
defaultValue bool Standardwert (Standard: false)

Befehlsergebnis

Die Methode handle() erhält ein CommandResult-Objekt mit typisierten Zugriffsmethoden zum Lesen von geparsten Optionen, Flags und Argumenten.

@override
Future<void> handle(CommandResult result) async {
  // Get a string option
  final name = result.getString('name');

  // Get a boolean flag
  final verbose = result.getBool('verbose');

  // Get an integer option
  final count = result.getInt('count');

  // Generic typed access
  final value = result.get<String>('key');

  // Built-in flag checks
  if (result.hasForceFlag) { /* --force was passed */ }
  if (result.hasHelpFlag) { /* --help was passed */ }

  // Raw arguments
  List<String> allArgs = result.arguments;
  List<String> unparsed = result.rest;
}
Methode / Eigenschaft Rückgabewert Beschreibung
getString(String name, {String? defaultValue}) String? Zeichenkettenwert abrufen
getBool(String name, {bool? defaultValue}) bool? Booleschen Wert abrufen
getInt(String name, {int? defaultValue}) int? Ganzzahlwert abrufen
get<T>(String name) T? Typisierten Wert abrufen
hasForceFlag bool Ob --force übergeben wurde
hasHelpFlag bool Ob --help übergeben wurde
arguments List<String> Alle Befehlszeilenargumente
rest List<String> Nicht geparste restliche Argumente

Interaktive Eingabe

NyCustomCommand stellt Methoden zur Erfassung von Benutzereingaben im Terminal bereit.

Texteingabe

// Ask a question with optional default
final name = prompt('What is your project name?', defaultValue: 'my_app');

// ask() is an alias for prompt()
final description = ask('Enter a description:');
Parameter Typ Beschreibung
question String Die anzuzeigende Frage
defaultValue String Standardwert, wenn der Benutzer Enter drückt (Standard: '')

Bestätigung

if (confirm('Would you like to continue?', defaultValue: true)) {
  await runProcess('flutter pub get');
} else {
  info('Operation canceled');
}
Parameter Typ Beschreibung
question String Die Ja/Nein-Frage
defaultValue bool Standardantwort (Standard: false)

Einfachauswahl

final environment = select(
  'Select deployment environment:',
  ['development', 'staging', 'production'],
  defaultOption: 'development',
);
Parameter Typ Beschreibung
question String Der Eingabeaufforderungstext
options List<String> Verfügbare Auswahlmöglichkeiten
defaultOption String? Vorausgewählte Option

Mehrfachauswahl

final packages = multiSelect(
  'Select packages to install:',
  ['firebase_auth', 'dio', 'provider', 'shared_preferences'],
);

if (packages.isNotEmpty) {
  addPackages(packages);
  await runProcess('flutter pub get');
}

Der Benutzer gibt kommagetrennte Nummern oder "all" ein.

Geheime Eingabe

final apiKey = promptSecret('Enter your API key:');

Die Eingabe wird in der Terminalanzeige verborgen. Fällt auf sichtbare Eingabe zurück, wenn der Echo-Modus nicht unterstützt wird.

Ausgabeformatierung

Methoden zur Ausgabe von formatiertem Text in der Konsole:

@override
Future<void> handle(CommandResult result) async {
  info('Processing files...');       // Blue text
  error('Operation failed');         // Red text
  success('Deployment complete');    // Green text
  warning('Outdated package');       // Yellow text
  line('Plain text output');         // No color
  comment('Background note');        // Gray text
  alert('Important notice');         // Bordered alert box
  newLine();                         // One blank line
  newLine(3);                        // Three blank lines

  // Exit the command with an error
  abort('Fatal error occurred');     // Prints red, exits with code 1
}
Methode Beschreibung
info(String message) Blauen Text ausgeben
error(String message) Roten Text ausgeben
success(String message) Grünen Text ausgeben
warning(String message) Gelben Text ausgeben
line(String message) Einfachen Text ausgeben (ohne Farbe)
newLine([int count = 1]) Leerzeilen ausgeben
comment(String message) Grauen/gedämpften Text ausgeben
alert(String message) Ein umrandetes Hinweisfeld ausgeben
abort([String? message, int exitCode = 1]) Den Befehl mit einem Fehler beenden

Spinner und Fortschritt

Spinner und Fortschrittsbalken bieten visuelles Feedback bei lang andauernden Operationen.

withSpinner verwenden

Umschließen Sie eine asynchrone Aufgabe mit einem automatischen Spinner:

final projectFiles = await withSpinner(
  task: () async {
    await sleep(2);
    return ['pubspec.yaml', 'lib/main.dart', 'README.md'];
  },
  message: 'Analyzing project structure',
  successMessage: 'Project analysis complete',
  errorMessage: 'Failed to analyze project',
);

info('Found ${projectFiles.length} key files');
Parameter Typ Beschreibung
task Future<T> Function() Die auszuführende asynchrone Funktion
message String Text, der während des Spinner-Laufs angezeigt wird
successMessage String? Wird bei Erfolg angezeigt
errorMessage String? Wird bei Fehler angezeigt

Manuelle Spinner-Steuerung

Für mehrstufige Workflows erstellen Sie einen Spinner und steuern ihn manuell:

final spinner = createSpinner('Deploying to production');
spinner.start();

try {
  await runProcess('flutter clean', silent: true);
  spinner.update('Building release version');

  await runProcess('flutter build web --release', silent: true);
  spinner.update('Uploading to server');

  await runProcess('./deploy.sh', silent: true);
  spinner.stop(completionMessage: 'Deployment completed', success: true);
} catch (e) {
  spinner.stop(completionMessage: 'Deployment failed: $e', success: false);
}

ConsoleSpinner-Methoden:

Methode Beschreibung
start([String? message]) Die Spinner-Animation starten
update(String message) Die angezeigte Nachricht ändern
stop({String? completionMessage, bool success = true}) Den Spinner stoppen

Fortschrittsbalken

Einen Fortschrittsbalken erstellen und manuell verwalten:

final progress = progressBar(100, message: 'Processing files');
progress.start();

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

progress.complete('All files processed');

ConsoleProgressBar-Methoden:

Methode / Eigenschaft Beschreibung
start() Den Fortschrittsbalken starten
tick([int amount = 1]) Fortschritt inkrementieren
update(int value) Fortschritt auf einen bestimmten Wert setzen
updateMessage(String newMessage) Die angezeigte Nachricht ändern
complete([String? completionMessage]) Mit optionaler Nachricht abschließen
stop() Stoppen ohne abzuschließen
current Aktueller Fortschrittswert (Getter)
percentage Fortschritt als Prozentsatz 0-100 (Getter)

Elemente mit Fortschritt verarbeiten

Eine Liste von Elementen mit automatischer Fortschrittsverfolgung verarbeiten:

// Async processing
final results = await withProgress<File, String>(
  items: findFiles('lib/', extension: '.dart'),
  process: (file, index) async {
    return file.path;
  },
  message: 'Analyzing Dart files',
  completionMessage: 'Analysis complete',
);

// Synchronous processing
final upperItems = withProgressSync<String, String>(
  items: ['a', 'b', 'c', 'd', 'e'],
  process: (item, index) => item.toUpperCase(),
  message: 'Converting items',
);

API-Helfer

Der api-Helfer bietet einen vereinfachten Wrapper um Dio für HTTP-Anfragen:

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

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

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

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

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

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

Kombinieren Sie es mit withSpinner für eine bessere Benutzererfahrung:

final data = await withSpinner(
  task: () => api((request) =>
    request.get('https://api.example.com/config')
  ),
  message: 'Loading configuration',
);

Der ApiService unterstützt die Methoden get, post, put, delete und patch, die jeweils optionale queryParameters, data, options und cancelToken akzeptieren.

Dateisystem-Helfer

Integrierte Dateisystem-Helfer, sodass Sie dart:io nicht importieren müssen:

// Check existence
if (fileExists('lib/config/app.dart')) { /* ... */ }
if (directoryExists('lib/app/models')) { /* ... */ }

// Read files
String content = await readFile('pubspec.yaml');
String contentSync = readFileSync('pubspec.yaml');

// Write files
await writeFile('lib/generated/output.dart', 'class Output {}');
writeFileSync('lib/generated/output.dart', 'class Output {}');

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

// Ensure a directory exists
await ensureDirectory('lib/generated');

// Delete and copy files
await deleteFile('lib/generated/output.dart');
await copyFile('lib/config/app.dart', 'lib/config/app.bak.dart');
Methode Beschreibung
fileExists(String path) Gibt true zurück, wenn die Datei existiert
directoryExists(String path) Gibt true zurück, wenn das Verzeichnis existiert
readFile(String path) Datei als Zeichenkette lesen (asynchron)
readFileSync(String path) Datei als Zeichenkette lesen (synchron)
writeFile(String path, String content) Inhalt in Datei schreiben (asynchron)
writeFileSync(String path, String content) Inhalt in Datei schreiben (synchron)
appendFile(String path, String content) Inhalt an Datei anhängen
ensureDirectory(String path) Verzeichnis erstellen, falls es nicht existiert
deleteFile(String path) Eine Datei löschen
copyFile(String source, String destination) Eine Datei kopieren

JSON- und YAML-Helfer

JSON- und YAML-Dateien mit integrierten Helfern lesen und schreiben:

// Read JSON as Map
Map<String, dynamic> config = await readJson('config.json');

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

// Write JSON (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 to a JSON array file (with duplicate prevention)
await appendToJsonArray(
  'lib/app/commands/commands.json',
  {'name': 'my_command', 'category': 'app', 'script': 'my_command.dart'},
  uniqueKey: 'name',
);

// Read YAML as Map
Map<String, dynamic> pubspec = await readYaml('pubspec.yaml');
info('Project: ${pubspec['name']}');
Methode Beschreibung
readJson(String path) JSON-Datei als Map<String, dynamic> lesen
readJsonArray(String path) JSON-Datei als List<dynamic> lesen
writeJson(String path, dynamic data, {bool pretty = true}) Daten als JSON schreiben
appendToJsonArray(String path, Map item, {String? uniqueKey}) An eine JSON-Array-Datei anhängen
readYaml(String path) YAML-Datei als Map<String, dynamic> lesen

Dart-Dateimanipulation

Helfer zum programmatischen Bearbeiten von Dart-Quelldateien -- nützlich beim Erstellen von Scaffolding-Werkzeugen:

// Add an import (skips if already present)
await addImport(
  'lib/bootstrap/providers.dart',
  "import '/app/providers/firebase_provider.dart';",
);

// Insert code before the last closing brace
await insertBeforeClosingBrace(
  'lib/bootstrap/providers.dart',
  '  FirebaseProvider(),',
);

// Check if a file contains a 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 Beschreibung
addImport(String filePath, String importStatement) Import zu Dart-Datei hinzufügen (überspringt, wenn vorhanden)
insertBeforeClosingBrace(String filePath, String code) Code vor letzter } in der Datei einfügen
fileContains(String filePath, String identifier) Prüfen, ob Datei eine Zeichenkette enthält
fileContainsPattern(String filePath, Pattern pattern) Prüfen, ob Datei einem Muster entspricht

Verzeichnis-Helfer

Helfer für die Arbeit mit Verzeichnissen und das Finden von Dateien:

// List directory contents
var entities = listDirectory('lib/app/models');
var allEntities = listDirectory('lib/', recursive: true);

// Find files by extension
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 recursively
await copyDirectory('lib/templates', 'lib/generated');
Methode Beschreibung
listDirectory(String path, {bool recursive = false}) Verzeichnisinhalte auflisten
findFiles(String directory, {String? extension, Pattern? namePattern, bool recursive = true}) Dateien nach Kriterien finden
deleteDirectory(String path) Verzeichnis rekursiv löschen
copyDirectory(String source, String destination) Verzeichnis rekursiv kopieren

Groß-/Kleinschreibungs-Helfer

Zeichenketten zwischen Namenskonventionen umwandeln, ohne das Paket recase importieren zu müssen:

String input = 'user profile page';

snakeCase(input);    // user_profile_page
camelCase(input);    // userProfilePage
pascalCase(input);   // UserProfilePage
titleCase(input);    // User Profile Page
kebabCase(input);    // user-profile-page
constantCase(input); // USER_PROFILE_PAGE
Methode Ausgabeformat Beispiel
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

Projektpfad-Helfer

Getter für Standard-Projektverzeichnisse von Nylo Website, die Pfade relativ zum Projektstamm zurückgeben:

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
String customPath = projectPath('app/services/auth_service.dart');
// Returns: lib/app/services/auth_service.dart
Eigenschaft Pfad
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) Löst einen relativen Pfad innerhalb des Projekts auf

Plattform-Helfer

Plattform prüfen und auf Umgebungsvariablen zugreifen:

if (isWindows) {
  info('Running on Windows');
} else if (isMacOS) {
  info('Running on macOS');
} else if (isLinux) {
  info('Running on Linux');
}

info('Working in: $workingDirectory');

String home = env('HOME', '/default/path');
Eigenschaft / Methode Beschreibung
isWindows true, wenn auf Windows ausgeführt
isMacOS true, wenn auf macOS ausgeführt
isLinux true, wenn auf Linux ausgeführt
workingDirectory Aktueller Arbeitsverzeichnispfad
env(String key, [String defaultValue = '']) System-Umgebungsvariable lesen

Dart- und Flutter-Befehle

Gängige Dart- und Flutter-CLI-Befehle als Hilfsmethoden ausführen. Jede gibt den Prozess-Exit-Code zurück:

// 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/');
Methode Beschreibung
dartFormat(String path) dart format auf eine Datei oder ein Verzeichnis ausführen
dartAnalyze([String? path]) dart analyze ausführen
flutterPubGet() flutter pub get ausführen
flutterClean() flutter clean ausführen
flutterBuild(String target, {List<String> args}) flutter build <target> ausführen
flutterTest([String? path]) flutter test ausführen

Validierungs-Helfer

Helfer zum Validieren und Bereinigen von Benutzereingaben für die Codegenerierung:

// Validate a Dart identifier
if (!isValidDartIdentifier('MyClass')) {
  error('Invalid Dart identifier');
}

// Require a non-empty first argument (aborts if missing)
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 Beschreibung
isValidDartIdentifier(String name) Einen Dart-Bezeichnernamen validieren
requireArgument(CommandResult result, {String? message}) Nicht-leeres erstes Argument erfordern oder abbrechen
cleanClassName(String name, {List<String> removeSuffixes}) Klassennamen bereinigen und in PascalCase umwandeln
cleanFileName(String name, {String extension = '.dart'}) Dateinamen bereinigen und in snake_case umwandeln

Datei-Scaffolding

Eine oder mehrere Dateien mit Inhalt über das Scaffolding-System erstellen.

Einzelne Datei

await scaffold(
  path: 'lib/app/services/auth_service.dart',
  content: '''
class AuthService {
  Future<bool> login(String email, String password) async {
    return false;
  }
}
''',
  force: false,
  successMessage: 'AuthService created',
);
Parameter Typ Beschreibung
path String Dateipfad zum Erstellen
content String Dateiinhalt
force bool Überschreiben, wenn vorhanden (Standard: false)
successMessage String? Nachricht, die bei Erfolg angezeigt wird

Mehrere Dateien

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

Die Klasse ScaffoldFile:

Eigenschaft Typ Beschreibung
path String Dateipfad zum Erstellen
content String Dateiinhalt
successMessage String? Nachricht, die bei Erfolg angezeigt wird

Task-Runner

Eine Reihe benannter Aufgaben mit automatischer Statusausgabe ausführen.

Einfacher Task-Runner

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

Task-Runner mit Spinner

await runTasksWithSpinner([
  CommandTask(
    'Preparing release',
    () async {
      await flutterClean();
      await flutterPubGet();
    },
  ),
  CommandTask(
    'Building APK',
    () => flutterBuild('apk', args: ['--release']),
  ),
]);

Die Klasse CommandTask:

Eigenschaft Typ Standard Beschreibung
name String erforderlich Aufgabenname, der in der Ausgabe angezeigt wird
action Future<void> Function() erforderlich Auszuführende asynchrone Funktion
stopOnError bool true Ob verbleibende Aufgaben bei Fehler gestoppt werden sollen

Tabellenausgabe

Formatierte ASCII-Tabellen in der Konsole anzeigen:

table(
  ['Name', 'Version', 'Status'],
  [
    ['nylo_framework', '7.0.0', 'installed'],
    ['nylo_support', '7.0.0', 'installed'],
    ['dio', '5.4.0', 'installed'],
  ],
);

Ausgabe:

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

Beispiele

Aktuelle-Uhrzeit-Befehl

Ein einfacher Befehl, der die aktuelle Uhrzeit anzeigt:

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

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

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");
    final now = DateTime.now();
    info("The current time is ${now.toIso8601String()}");
    info("Requested format: $format");
  }
}

Schriften-herunterladen-Befehl

Ein Befehl, der Google Fonts herunterlädt und im Projekt installiert:

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

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

class _DownloadFontsCommand extends NyCustomCommand {
  _DownloadFontsCommand(super.arguments);

  @override
  CommandBuilder builder(CommandBuilder command) {
    command.addOption('font', abbr: 'f', help: 'Font family name');
    command.addFlag('verbose', abbr: 'v', defaultValue: false);
    return command;
  }

  @override
  Future<void> handle(CommandResult result) async {
    final fontName = result.getString('font') ??
        prompt('Enter font family name:', defaultValue: 'Roboto');

    final verbose = result.getBool('verbose') ?? false;

    await withSpinner(
      task: () async {
        await ensureDirectory('assets/fonts');
        final fontData = await api((request) =>
          request.get('https://fonts.google.com/download?family=$fontName')
        );
        if (fontData != null) {
          await writeFile('assets/fonts/$fontName.ttf', fontData.toString());
        }
      },
      message: 'Downloading $fontName font',
      successMessage: '$fontName font installed',
      errorMessage: 'Failed to download $fontName',
    );

    if (verbose) {
      info('Font saved to: assets/fonts/$fontName.ttf');
    }
  }
}

Deployment-Pipeline-Befehl

Ein Befehl, der eine vollständige Deployment-Pipeline mit dem Task-Runner ausführt:

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

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

class _DeployCommand extends NyCustomCommand {
  _DeployCommand(super.arguments);

  @override
  CommandBuilder builder(CommandBuilder command) {
    command.addOption(
      'environment',
      abbr: 'e',
      defaultValue: 'development',
      allowed: ['development', 'staging', 'production'],
    );
    command.addFlag('skip-tests', defaultValue: false);
    return command;
  }

  @override
  Future<void> handle(CommandResult result) async {
    final env = result.getString('environment') ?? 'development';
    final skipTests = result.getBool('skip-tests') ?? false;

    alert('Deploying to $env');
    newLine();

    if (env == 'production') {
      if (!confirm('Are you sure you want to deploy to production?')) {
        abort('Deployment canceled');
      }
    }

    final tasks = <CommandTask>[
      CommandTask('Clean project', () => flutterClean()),
      CommandTask('Fetch dependencies', () => flutterPubGet()),
    ];

    if (!skipTests) {
      tasks.add(CommandTask(
        'Run tests',
        () => flutterTest(),
        stopOnError: true,
      ));
    }

    tasks.add(CommandTask(
      'Build for web',
      () => flutterBuild('web', args: ['--release']),
    ));

    await runTasksWithSpinner(tasks);

    newLine();
    success('Deployment to $env completed');

    table(
      ['Step', 'Status'],
      tasks.map((t) => [t.name, 'Done']).toList(),
    );
  }
}