Basics

Alat CLI Metro

Pengantar

Metro adalah alat CLI yang bekerja di balik layar framework Nylo Website. Alat ini menyediakan banyak tools yang berguna untuk mempercepat pengembangan.

Instalasi

Ketika Anda membuat proyek Nylo baru menggunakan nylo init, perintah metro akan otomatis dikonfigurasi untuk terminal Anda. Anda dapat langsung menggunakannya di proyek Nylo mana pun.

Jalankan metro dari direktori proyek Anda untuk melihat semua perintah yang tersedia:

metro

Anda akan melihat output yang mirip dengan di bawah ini.

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

Membuat controller

Membuat controller baru

Anda dapat membuat controller baru dengan menjalankan perintah berikut di terminal.

metro make:controller profile_controller

Ini akan membuat controller baru jika belum ada di dalam direktori lib/app/controllers/.

Membuat controller secara paksa

Argumen:

Menggunakan flag --force atau -f akan menimpa controller yang sudah ada jika sudah ada.

metro make:controller profile_controller --force

Membuat model

Membuat model baru

Anda dapat membuat model baru dengan menjalankan perintah berikut di terminal.

metro make:model product

Ini akan menempatkan model yang baru dibuat di lib/app/models/.

Membuat model dari JSON

Argumen:

Menggunakan flag --json atau -j akan membuat model baru dari payload JSON.

metro make:model product --json

Kemudian, Anda dapat menempelkan JSON Anda ke terminal dan itu akan menghasilkan model untuk Anda.

Membuat model secara paksa

Argumen:

Menggunakan flag --force atau -f akan menimpa model yang sudah ada jika sudah ada.

metro make:model product --force

Membuat halaman

Membuat halaman baru

Anda dapat membuat halaman baru dengan menjalankan perintah berikut di terminal.

metro make:page product_page

Ini akan membuat halaman baru jika belum ada di dalam direktori lib/resources/pages/.

Membuat halaman dengan controller

Anda dapat membuat halaman baru dengan controller dengan menjalankan perintah berikut di terminal.

Argumen:

Menggunakan flag --controller atau -c akan membuat halaman baru dengan controller.

metro make:page product_page -c

Membuat halaman auth

Anda dapat membuat halaman auth baru dengan menjalankan perintah berikut di terminal.

Argumen:

Menggunakan flag --auth atau -a akan membuat halaman auth baru.

metro make:page login_page -a

Membuat halaman initial

Anda dapat membuat halaman initial baru dengan menjalankan perintah berikut di terminal.

Argumen:

Menggunakan flag --initial atau -i akan membuat halaman initial baru.

metro make:page home_page -i

Membuat halaman secara paksa

Argumen:

Menggunakan flag --force atau -f akan menimpa halaman yang sudah ada jika sudah ada.

metro make:page product_page --force

Membuat stateless widget

Membuat stateless widget baru

Anda dapat membuat stateless widget baru dengan menjalankan perintah berikut di terminal.

metro make:stateless_widget product_rating_widget

Perintah di atas akan membuat widget baru jika belum ada di dalam direktori lib/resources/widgets/.

Membuat stateless widget secara paksa

Argumen:

Menggunakan flag --force atau -f akan menimpa widget yang sudah ada jika sudah ada.

metro make:stateless_widget product_rating_widget --force

Membuat stateful widget

Membuat stateful widget baru

Anda dapat membuat stateful widget baru dengan menjalankan perintah berikut di terminal.

metro make:stateful_widget product_rating_widget

Perintah di atas akan membuat widget baru jika belum ada di dalam direktori lib/resources/widgets/.

Membuat stateful widget secara paksa

Argumen:

Menggunakan flag --force atau -f akan menimpa widget yang sudah ada jika sudah ada.

metro make:stateful_widget product_rating_widget --force

Membuat journey widget

Membuat journey widget baru

Anda dapat membuat journey widget baru dengan menjalankan perintah berikut di 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"

Perintah di atas akan membuat widget baru jika belum ada di dalam direktori lib/resources/widgets/.

Argumen --parent digunakan untuk menentukan widget induk tempat journey widget baru akan ditambahkan.

Contoh

metro make:navigation_hub onboarding

Selanjutnya, tambahkan journey widget baru.

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

Membuat journey widget secara paksa

Argumen: Menggunakan flag --force atau -f akan menimpa widget yang sudah ada jika sudah ada.

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

Membuat API Service

Membuat API Service baru

Anda dapat membuat API service baru dengan menjalankan perintah berikut di terminal.

metro make:api_service user_api_service

Ini akan menempatkan API service yang baru dibuat di lib/app/networking/.

Membuat API Service baru dengan model

Anda dapat membuat API service baru dengan model dengan menjalankan perintah berikut di terminal.

Argumen:

Menggunakan opsi --model atau -m akan membuat API service baru dengan model.

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

Ini akan menempatkan API service yang baru dibuat di lib/app/networking/.

Membuat API Service secara paksa

Argumen:

Menggunakan flag --force atau -f akan menimpa API Service yang sudah ada jika sudah ada.

metro make:api_service user --force

Membuat event

Membuat event baru

Anda dapat membuat event baru dengan menjalankan perintah berikut di terminal.

metro make:event login_event

Ini akan membuat event baru di lib/app/events.

Membuat event secara paksa

Argumen:

Menggunakan flag --force atau -f akan menimpa event yang sudah ada jika sudah ada.

metro make:event login_event --force

Membuat provider

Membuat provider baru

Buat provider baru dalam aplikasi Anda menggunakan perintah di bawah ini.

metro make:provider firebase_provider

Ini akan menempatkan provider yang baru dibuat di lib/app/providers/.

Membuat provider secara paksa

Argumen:

Menggunakan flag --force atau -f akan menimpa provider yang sudah ada jika sudah ada.

metro make:provider firebase_provider --force

Membuat theme

Membuat theme baru

Anda dapat membuat theme dengan menjalankan perintah berikut di terminal.

metro make:theme bright_theme

Ini akan membuat theme baru di lib/resources/themes/.

Membuat theme secara paksa

Argumen:

Menggunakan flag --force atau -f akan menimpa theme yang sudah ada jika sudah ada.

metro make:theme bright_theme --force

Membuat Forms

Membuat form baru

Anda dapat membuat form baru dengan menjalankan perintah berikut di terminal.

metro make:form car_advert_form

Ini akan membuat form baru di lib/app/forms.

Membuat form secara paksa

Argumen:

Menggunakan flag --force atau -f akan menimpa form yang sudah ada jika sudah ada.

metro make:form login_form --force

Membuat Route Guard

Membuat route guard baru

Anda dapat membuat route guard dengan menjalankan perintah berikut di terminal.

metro make:route_guard premium_content

Ini akan membuat route guard baru di lib/app/route_guards.

Membuat route guard secara paksa

Argumen:

Menggunakan flag --force atau -f akan menimpa route guard yang sudah ada jika sudah ada.

metro make:route_guard premium_content --force

Membuat Config File

Membuat config file baru

Anda dapat membuat config file baru dengan menjalankan perintah berikut di terminal.

metro make:config shopping_settings

Ini akan membuat config file baru di lib/app/config.

Membuat config file secara paksa

Argumen:

Menggunakan flag --force atau -f akan menimpa config file yang sudah ada jika sudah ada.

metro make:config app_config --force

Membuat Command

Membuat command baru

Anda dapat membuat command baru dengan menjalankan perintah berikut di terminal.

metro make:command my_command

Ini akan membuat command baru di lib/app/commands.

Membuat command secara paksa

Argumen: Menggunakan flag --force atau -f akan menimpa command yang sudah ada jika sudah ada.

metro make:command my_command --force

Membuat State Managed Widget

Anda dapat membuat state managed widget baru dengan menjalankan perintah berikut di terminal.

metro make:state_managed_widget product_rating_widget

Perintah di atas akan membuat widget baru di lib/resources/widgets/.

Menggunakan flag --force atau -f akan menimpa widget yang sudah ada jika sudah ada.

metro make:state_managed_widget product_rating_widget --force

Membuat Navigation Hub

Anda dapat membuat navigation hub baru dengan menjalankan perintah berikut di terminal.

metro make:navigation_hub dashboard

Ini akan membuat navigation hub baru di lib/resources/pages/ dan menambahkan route secara otomatis.

Argumen:

Flag Singkat Deskripsi
--auth -a Buat sebagai halaman auth
--initial -i Buat sebagai halaman initial
--force -f Timpa jika sudah ada
# Create as the initial page
metro make:navigation_hub dashboard --initial

Membuat Bottom Sheet Modal

Anda dapat membuat bottom sheet modal baru dengan menjalankan perintah berikut di terminal.

metro make:bottom_sheet_modal payment_options

Ini akan membuat bottom sheet modal baru di lib/resources/widgets/.

Menggunakan flag --force atau -f akan menimpa modal yang sudah ada jika sudah ada.

metro make:bottom_sheet_modal payment_options --force

Membuat Button

Anda dapat membuat widget button baru dengan menjalankan perintah berikut di terminal.

metro make:button checkout_button

Ini akan membuat widget button baru di lib/resources/widgets/.

Menggunakan flag --force atau -f akan menimpa button yang sudah ada jika sudah ada.

metro make:button checkout_button --force

Membuat Interceptor

Anda dapat membuat network interceptor baru dengan menjalankan perintah berikut di terminal.

metro make:interceptor auth_interceptor

Ini akan membuat interceptor baru di lib/app/networking/dio/interceptors/.

Menggunakan flag --force atau -f akan menimpa interceptor yang sudah ada jika sudah ada.

metro make:interceptor auth_interceptor --force

Membuat Env File

Anda dapat membuat file environment baru dengan menjalankan perintah berikut di terminal.

metro make:env .env.staging

Ini akan membuat file .env baru di root proyek Anda.

Membuat Key

Generate APP_KEY yang aman untuk enkripsi environment. Ini digunakan untuk file .env terenkripsi di v7.

metro make:key

Argumen:

Flag / Opsi Singkat Deskripsi
--force -f Timpa APP_KEY yang sudah ada
--file -e File .env target (default: .env)
# Generate key and overwrite existing
metro make:key --force

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

Membangun App Icons

Anda dapat menghasilkan semua app icons untuk IOS dan Android dengan menjalankan perintah di bawah ini.

dart run flutter_launcher_icons:main

Ini menggunakan konfigurasi flutter_icons di file pubspec.yaml Anda.

Perintah Kustom

Perintah kustom memungkinkan Anda memperluas CLI Nylo dengan perintah khusus proyek Anda sendiri. Fitur ini memungkinkan Anda mengotomatisasi tugas berulang, menerapkan alur kerja deployment, atau menambahkan fungsionalitas kustom apa pun langsung ke alat command-line proyek Anda.

Catatan: Saat ini Anda tidak dapat mengimpor nylo_framework.dart di perintah kustom Anda, silakan gunakan ny_cli.dart sebagai gantinya.

Membuat Perintah Kustom

Untuk membuat perintah kustom baru, Anda dapat menggunakan fitur make:command:

metro make:command current_time

Anda dapat menentukan kategori untuk perintah Anda menggunakan opsi --category:

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

Ini akan membuat file perintah baru di lib/app/commands/current_time.dart dengan struktur berikut:

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

Perintah akan secara otomatis terdaftar di file lib/app/commands/commands.json, yang berisi daftar semua perintah yang terdaftar:

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

Menjalankan Perintah Kustom

Setelah dibuat, Anda dapat menjalankan perintah kustom Anda menggunakan shorthand Metro atau perintah Dart lengkap:

metro app:current_time

Ketika Anda menjalankan metro tanpa argumen, Anda akan melihat perintah kustom Anda tercantum di menu di bawah bagian "Custom Commands":

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

Untuk menampilkan informasi bantuan untuk perintah Anda, gunakan flag --help atau -h:

metro project:install_firebase --help

Menambahkan Opsi ke Perintah

Opsi memungkinkan perintah Anda menerima input tambahan dari pengguna. Anda dapat menambahkan opsi ke perintah Anda di metode 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;
}

Kemudian akses nilai opsi di metode handle perintah Anda:

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

  // Command implementation...
}

Contoh penggunaan:

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

Menambahkan Flag ke Perintah

Flag adalah opsi boolean yang dapat diaktifkan atau dinonaktifkan. Tambahkan flag ke perintah Anda menggunakan metode 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;
}

Kemudian periksa status flag di metode handle perintah Anda:

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

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

  // Command implementation...
}

Contoh penggunaan:

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

Metode Helper

Kelas dasar NyCustomCommand menyediakan beberapa metode helper untuk membantu tugas umum:

Mencetak Pesan

Berikut adalah beberapa metode untuk mencetak pesan dengan warna berbeda:

info Cetak pesan info dalam teks biru
error Cetak pesan error dalam teks merah
success Cetak pesan sukses dalam teks hijau
warning Cetak pesan peringatan dalam teks kuning

Menjalankan Proses

Jalankan proses dan tampilkan outputnya di konsol:

addPackage Tambahkan paket ke pubspec.yaml
addPackages Tambahkan beberapa paket ke pubspec.yaml
runProcess Jalankan proses eksternal dan tampilkan output di konsol
prompt Kumpulkan input teks dari pengguna
confirm Ajukan pertanyaan ya/tidak dan dapatkan hasil boolean
select Tampilkan daftar opsi dan biarkan pengguna memilih satu
multiSelect Biarkan pengguna memilih beberapa opsi dari daftar

Permintaan Jaringan

Membuat permintaan jaringan melalui konsol:

api Buat panggilan API menggunakan klien API Nylo

Spinner Loading

Tampilkan spinner loading saat menjalankan fungsi:

withSpinner Tampilkan spinner loading saat menjalankan fungsi
createSpinner Buat instance spinner untuk kontrol manual

Helper Perintah Kustom

Anda juga dapat menggunakan metode helper berikut untuk mengelola argumen perintah:

getString Dapatkan nilai string dari argumen perintah
getBool Dapatkan nilai boolean dari argumen perintah
getInt Dapatkan nilai integer dari argumen perintah
sleep Jeda eksekusi selama durasi tertentu

Menjalankan Proses Eksternal

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

Manajemen Paket

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

Format 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

Metode Input Interaktif

Kelas dasar NyCustomCommand menyediakan beberapa metode untuk mengumpulkan input pengguna di terminal. Metode-metode ini memudahkan pembuatan antarmuka command-line interaktif untuk perintah kustom Anda.

Input Teks

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

Menampilkan pertanyaan kepada pengguna dan mengumpulkan respons teks mereka.

Parameter:

  • question: Pertanyaan atau prompt yang ditampilkan
  • defaultValue: Nilai default opsional jika pengguna hanya menekan Enter

Mengembalikan: Input pengguna sebagai string, atau nilai default jika tidak ada input yang diberikan

Contoh:

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

Konfirmasi

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

Mengajukan pertanyaan ya/tidak kepada pengguna dan mengembalikan hasil boolean.

Parameter:

  • question: Pertanyaan ya/tidak yang diajukan
  • defaultValue: Jawaban default (true untuk ya, false untuk tidak)

Mengembalikan: true jika pengguna menjawab ya, false jika menjawab tidak

Contoh:

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

Seleksi Tunggal

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

Menampilkan daftar opsi dan membiarkan pengguna memilih satu.

Parameter:

  • question: Prompt seleksi
  • options: Daftar opsi yang tersedia
  • defaultOption: Seleksi default opsional

Mengembalikan: Opsi yang dipilih sebagai string

Contoh:

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

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

Seleksi Ganda

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

Memungkinkan pengguna memilih beberapa opsi dari daftar.

Parameter:

  • question: Prompt seleksi
  • options: Daftar opsi yang tersedia

Mengembalikan: Daftar opsi yang dipilih

Contoh:

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

Metode Helper API

Metode helper api menyederhanakan pembuatan permintaan jaringan dari perintah kustom Anda.

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

Contoh Penggunaan Dasar

Permintaan GET

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

Permintaan POST

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

Permintaan PUT

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

Permintaan DELETE

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

Permintaan PATCH

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

Dengan Parameter Query

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

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

Fungsionalitas Spinner

Spinner memberikan umpan balik visual selama operasi yang berjalan lama di perintah kustom Anda. Mereka menampilkan indikator animasi beserta pesan saat perintah Anda mengeksekusi tugas asinkron, meningkatkan pengalaman pengguna dengan menunjukkan progres dan status.

Menggunakan withSpinner

Metode withSpinner memungkinkan Anda membungkus tugas asinkron dengan animasi spinner yang secara otomatis dimulai saat tugas dimulai dan berhenti saat selesai atau gagal:

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

Parameter:

  • task: Fungsi asinkron yang akan dieksekusi
  • message: Teks yang ditampilkan saat spinner berjalan
  • successMessage: Pesan opsional yang ditampilkan saat berhasil
  • errorMessage: Pesan opsional yang ditampilkan jika tugas gagal

Mengembalikan: Hasil dari fungsi tugas

Contoh:

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

Kontrol Spinner Manual

Untuk skenario yang lebih kompleks di mana Anda perlu mengontrol status spinner secara manual, Anda dapat membuat instance spinner:

ConsoleSpinner createSpinner(String message)

Parameter:

  • message: Teks yang ditampilkan saat spinner berjalan

Mengembalikan: Instance ConsoleSpinner yang dapat Anda kontrol secara manual

Contoh dengan kontrol manual:

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

Contoh

Tugas Sederhana dengan 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',
  );
}

Beberapa Operasi Berurutan

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

Alur Kerja Kompleks dengan Kontrol Manual

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

Menggunakan spinner di perintah kustom Anda memberikan umpan balik visual yang jelas kepada pengguna selama operasi yang berjalan lama, menciptakan pengalaman command-line yang lebih rapi dan profesional.

Mendapatkan nilai string dari opsi

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

Parameter:

  • name: Nama opsi yang akan diambil
  • defaultValue: Nilai default opsional jika opsi tidak disediakan

Mengembalikan: Nilai opsi sebagai string

Contoh:

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

Mendapatkan nilai bool dari opsi

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

Parameter:

  • name: Nama opsi yang akan diambil
  • defaultValue: Nilai default opsional jika opsi tidak disediakan

Mengembalikan: Nilai opsi sebagai boolean

Contoh:

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

Mendapatkan nilai int dari opsi

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

Parameter:

  • name: Nama opsi yang akan diambil
  • defaultValue: Nilai default opsional jika opsi tidak disediakan

Mengembalikan: Nilai opsi sebagai integer

Contoh:

@override
CommandBuilder builder(CommandBuilder command) {
  command.addOption("count", defaultValue: 5);
  return command;
}

Future<void> handle(CommandResult result) async {
  final count = result.getInt('count');
  info('Count is set to $count');
}

Sleep selama durasi tertentu

void sleep(int seconds)

Parameter:

  • seconds: Jumlah detik untuk sleep

Mengembalikan: Tidak ada

Contoh:

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

Format Output

Selain metode dasar info, error, success, dan warning, NyCustomCommand menyediakan helper output tambahan:

@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
  }
}
Metode Deskripsi
line(String message) Cetak teks biasa tanpa warna
newLine([int count = 1]) Cetak baris kosong
comment(String message) Cetak teks abu-abu/muted
alert(String message) Cetak kotak peringatan yang menonjol
ask(String question, {String defaultValue}) Alias untuk prompt
promptSecret(String question) Input tersembunyi untuk data sensitif
abort([String? message, int exitCode = 1]) Keluar dari perintah dengan error

Helper File System

NyCustomCommand menyertakan helper file system bawaan sehingga Anda tidak perlu mengimpor dart:io secara manual untuk operasi umum.

Membaca dan Menulis 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');
}
Metode Deskripsi
fileExists(String path) Mengembalikan true jika file ada
directoryExists(String path) Mengembalikan true jika direktori ada
readFile(String path) Baca file sebagai string (async)
readFileSync(String path) Baca file sebagai string (sync)
writeFile(String path, String content) Tulis konten ke file (async)
writeFileSync(String path, String content) Tulis konten ke file (sync)
appendFile(String path, String content) Tambahkan konten ke file
ensureDirectory(String path) Buat direktori jika belum ada
deleteFile(String path) Hapus file
copyFile(String source, String destination) Salin file

Helper JSON dan YAML

Baca dan tulis file JSON dan YAML dengan helper bawaan.

@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']}');
}
Metode Deskripsi
readJson(String path) Baca file JSON sebagai Map<String, dynamic>
readJsonArray(String path) Baca file JSON sebagai List<dynamic>
writeJson(String path, dynamic data, {bool pretty = true}) Tulis data sebagai JSON
appendToJsonArray(String path, Map item, {String? uniqueKey}) Tambahkan ke file array JSON
readYaml(String path) Baca file YAML sebagai Map<String, dynamic>

Helper Konversi Case

Konversi string antar konvensi penamaan tanpa mengimpor paket 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
}
Metode Format Output Contoh
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 Path Proyek

Getter untuk direktori proyek Nylo Website standar. Ini mengembalikan path relatif terhadap root proyek.

@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');
}
Properti Path
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) Resolusi path relatif dalam proyek

Helper Platform

Periksa platform dan akses variabel environment.

@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');
}
Properti / Metode Deskripsi
isWindows true jika berjalan di Windows
isMacOS true jika berjalan di macOS
isLinux true jika berjalan di Linux
workingDirectory Path direktori kerja saat ini
env(String key, [String defaultValue = '']) Baca variabel environment sistem

Perintah Dart dan Flutter

Jalankan perintah CLI Dart dan Flutter umum sebagai metode helper. Masing-masing mengembalikan kode exit proses.

@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
}
Metode Deskripsi
dartFormat(String path) Jalankan dart format pada file atau direktori
dartAnalyze([String? path]) Jalankan dart analyze
flutterPubGet() Jalankan flutter pub get
flutterClean() Jalankan flutter clean
flutterBuild(String target, {List<String> args}) Jalankan flutter build <target>
flutterTest([String? path]) Jalankan flutter test

Manipulasi File Dart

Helper untuk mengedit file Dart secara programatik, berguna saat membangun alat 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'),
  );
}
Metode Deskripsi
addImport(String filePath, String importStatement) Tambahkan import ke file Dart (lewati jika sudah ada)
insertBeforeClosingBrace(String filePath, String code) Sisipkan kode sebelum } terakhir di file
fileContains(String filePath, String identifier) Periksa apakah file mengandung string
fileContainsPattern(String filePath, Pattern pattern) Periksa apakah file cocok dengan pola

Helper Direktori

Helper untuk bekerja dengan direktori dan menemukan 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');
}
Metode Deskripsi
listDirectory(String path, {bool recursive = false}) Daftar isi direktori
findFiles(String directory, {String? extension, Pattern? namePattern, bool recursive = true}) Temukan file yang cocok dengan kriteria
deleteDirectory(String path) Hapus direktori secara rekursif
copyDirectory(String source, String destination) Salin direktori secara rekursif

Helper Validasi

Helper untuk memvalidasi dan membersihkan input pengguna untuk pembuatan kode.

@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'
}
Metode Deskripsi
isValidDartIdentifier(String name) Validasi nama identifier Dart
requireArgument(CommandResult result, {String? message}) Wajibkan argumen pertama non-kosong atau batalkan
cleanClassName(String name, {List<String> removeSuffixes}) Bersihkan dan PascalCase nama kelas
cleanFileName(String name, {String extension = '.dart'}) Bersihkan dan snake_case nama file

Scaffolding File

Buat satu atau banyak file dengan konten menggunakan sistem scaffolding.

File Tunggal

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

Banyak File

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

Kelas ScaffoldFile menerima:

Properti Tipe Deskripsi
path String Path file yang akan dibuat
content String Konten file
successMessage String? Pesan yang ditampilkan saat berhasil

Task Runner

Jalankan serangkaian tugas bernama dengan output status otomatis.

Task Runner Dasar

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

Kelas CommandTask menerima:

Properti Tipe Default Deskripsi
name String wajib Nama tugas yang ditampilkan di output
action Future<void> Function() wajib Fungsi async yang akan dieksekusi
stopOnError bool true Apakah akan menghentikan tugas tersisa jika ini gagal

Output Tabel

Tampilkan tabel ASCII terformat di konsol.

@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 │
└─────────────────┴─────────┴───────────┘

Progress Bar

Tampilkan progress bar untuk operasi dengan jumlah item yang diketahui.

Progress Bar Manual

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

Memproses Item dengan Progress

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

Progress Sinkron

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

Kelas ConsoleProgressBar menyediakan:

Metode Deskripsi
start() Mulai progress bar
tick([int amount = 1]) Naikkan progress
update(int value) Set progress ke nilai tertentu
updateMessage(String newMessage) Ubah pesan yang ditampilkan
complete([String? completionMessage]) Selesaikan dengan pesan opsional
stop() Berhenti tanpa menyelesaikan
current Nilai progress saat ini (getter)
percentage Progress sebagai persentase (getter)