Basics

Metro CLI 工具

简介

Metro 是一个在 Nylo Website 框架底层运行的 CLI 工具。 它提供了许多有用的工具来加速开发。

安装

当您使用 nylo init 创建新的 Nylo 项目时,metro 命令会自动为您的终端配置。您可以在任何 Nylo 项目中立即开始使用它。

从项目目录运行 metro 来查看所有可用命令:

metro

您应该看到类似以下的输出。

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

生成控制器

创建新控制器

您可以通过在终端中运行以下命令来创建新控制器。

metro make:controller profile_controller

如果 lib/app/controllers/ 目录中不存在该控制器,将会创建一个新的。

强制创建控制器

参数:

使用 --force-f 标志将覆盖已存在的控制器。

metro make:controller profile_controller --force

生成模型

创建新模型

您可以通过在终端中运行以下命令来创建新模型。

metro make:model product

新创建的模型将放置在 lib/app/models/ 中。

从 JSON 创建模型

参数:

使用 --json-j 标志将从 JSON 数据创建新模型。

metro make:model product --json

然后,您可以将 JSON 粘贴到终端中,它将为您生成模型。

强制创建模型

参数:

使用 --force-f 标志将覆盖已存在的模型。

metro make:model product --force

生成页面

创建新页面

您可以通过在终端中运行以下命令来创建新页面。

metro make:page product_page

如果 lib/resources/pages/ 目录中不存在该页面,将会创建一个新的。

创建带控制器的页面

您可以通过在终端中运行以下命令来创建带控制器的新页面。

参数:

使用 --controller-c 标志将创建带控制器的新页面。

metro make:page product_page -c

创建认证页面

您可以通过在终端中运行以下命令来创建新认证页面。

参数:

使用 --auth-a 标志将创建新认证页面。

metro make:page login_page -a

创建初始页面

您可以通过在终端中运行以下命令来创建新初始页面。

参数:

使用 --initial-i 标志将创建新初始页面。

metro make:page home_page -i

强制创建页面

参数:

使用 --force-f 标志将覆盖已存在的页面。

metro make:page product_page --force

生成无状态组件

创建新无状态组件

您可以通过在终端中运行以下命令来创建新无状态组件。

metro make:stateless_widget product_rating_widget

上述命令将在 lib/resources/widgets/ 目录中创建一个新组件(如果不存在)。

强制创建无状态组件

参数:

使用 --force-f 标志将覆盖已存在的组件。

metro make:stateless_widget product_rating_widget --force

生成有状态组件

创建新有状态组件

您可以通过在终端中运行以下命令来创建新有状态组件。

metro make:stateful_widget product_rating_widget

上述命令将在 lib/resources/widgets/ 目录中创建一个新组件(如果不存在)。

强制创建有状态组件

参数:

使用 --force-f 标志将覆盖已存在的组件。

metro make:stateful_widget product_rating_widget --force

生成旅程组件

创建新旅程组件

您可以通过在终端中运行以下命令来创建新旅程组件。

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"

上述命令将在 lib/resources/widgets/ 目录中创建一个新组件(如果不存在)。

--parent 参数用于指定新旅程组件将添加到的父组件。

示例

metro make:navigation_hub onboarding

接下来,添加新的旅程组件。

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

强制创建旅程组件

参数: 使用 --force-f 标志将覆盖已存在的组件。

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

生成 API 服务

创建新 API 服务

您可以通过在终端中运行以下命令来创建新 API 服务。

metro make:api_service user_api_service

新创建的 API 服务将放置在 lib/app/networking/ 中。

创建带模型的新 API 服务

您可以通过在终端中运行以下命令来创建带模型的新 API 服务。

参数:

使用 --model-m 选项将创建带模型的新 API 服务。

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

新创建的 API 服务将放置在 lib/app/networking/ 中。

强制创建 API 服务

参数:

使用 --force-f 标志将覆盖已存在的 API 服务。

metro make:api_service user --force

生成事件

创建新事件

您可以通过在终端中运行以下命令来创建新事件。

metro make:event login_event

这将在 lib/app/events 中创建一个新事件。

强制创建事件

参数:

使用 --force-f 标志将覆盖已存在的事件。

metro make:event login_event --force

生成 Provider

创建新 provider

使用以下命令在您的应用程序中创建新 provider。

metro make:provider firebase_provider

新创建的 provider 将放置在 lib/app/providers/ 中。

强制创建 provider

参数:

使用 --force-f 标志将覆盖已存在的 provider。

metro make:provider firebase_provider --force

生成主题

创建新主题

您可以通过在终端中运行以下命令来创建主题。

metro make:theme bright_theme

这将在 lib/resources/themes/ 中创建一个新主题。

强制创建主题

参数:

使用 --force-f 标志将覆盖已存在的主题。

metro make:theme bright_theme --force

生成表单

创建新表单

您可以通过在终端中运行以下命令来创建新表单。

metro make:form car_advert_form

这将在 lib/app/forms 中创建一个新表单。

强制创建表单

参数:

使用 --force-f 标志将覆盖已存在的表单。

metro make:form login_form --force

生成路由守卫

创建新路由守卫

您可以通过在终端中运行以下命令来创建路由守卫。

metro make:route_guard premium_content

这将在 lib/app/route_guards 中创建一个新路由守卫。

强制创建路由守卫

参数:

使用 --force-f 标志将覆盖已存在的路由守卫。

metro make:route_guard premium_content --force

生成配置文件

创建新配置文件

您可以通过在终端中运行以下命令来创建新配置文件。

metro make:config shopping_settings

这将在 lib/app/config 中创建一个新配置文件。

强制创建配置文件

参数:

使用 --force-f 标志将覆盖已存在的配置文件。

metro make:config app_config --force

生成命令

创建新命令

您可以通过在终端中运行以下命令来创建新命令。

metro make:command my_command

这将在 lib/app/commands 中创建一个新命令。

强制创建命令

参数: 使用 --force-f 标志将覆盖已存在的命令。

metro make:command my_command --force

生成状态管理组件

您可以通过在终端中运行以下命令来创建新的状态管理组件。

metro make:state_managed_widget product_rating_widget

上述命令将在 lib/resources/widgets/ 中创建一个新组件。

使用 --force-f 标志将覆盖已存在的组件。

metro make:state_managed_widget product_rating_widget --force

生成导航中心

您可以通过在终端中运行以下命令来创建新的导航中心。

metro make:navigation_hub dashboard

这将在 lib/resources/pages/ 中创建一个新的导航中心并自动添加路由。

参数:

标志 简写 描述
--auth -a 创建为认证页面
--initial -i 创建为初始页面
--force -f 如果存在则覆盖
# Create as the initial page
metro make:navigation_hub dashboard --initial

生成底部弹窗

您可以通过在终端中运行以下命令来创建新的底部弹窗。

metro make:bottom_sheet_modal payment_options

这将在 lib/resources/widgets/ 中创建一个新的底部弹窗。

使用 --force-f 标志将覆盖已存在的弹窗。

metro make:bottom_sheet_modal payment_options --force

生成按钮

您可以通过在终端中运行以下命令来创建新的按钮组件。

metro make:button checkout_button

这将在 lib/resources/widgets/ 中创建一个新的按钮组件。

使用 --force-f 标志将覆盖已存在的按钮。

metro make:button checkout_button --force

生成拦截器

您可以通过在终端中运行以下命令来创建新的网络拦截器。

metro make:interceptor auth_interceptor

这将在 lib/app/networking/dio/interceptors/ 中创建一个新的拦截器。

使用 --force-f 标志将覆盖已存在的拦截器。

metro make:interceptor auth_interceptor --force

生成环境文件

您可以通过在终端中运行以下命令来创建新的环境文件。

metro make:env .env.staging

这将在项目根目录中创建一个新的 .env 文件。

生成密钥

为环境加密生成安全的 APP_KEY。这用于 v7 中的加密 .env 文件。

metro make:key

参数:

标志 / 选项 简写 描述
--force -f 覆盖现有 APP_KEY
--file -e 目标 .env 文件(默认:.env
# Generate key and overwrite existing
metro make:key --force

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

构建应用图标

您可以通过运行以下命令来生成 IOS 和 Android 的所有应用图标。

dart run flutter_launcher_icons:main

这使用 pubspec.yaml 文件中的 flutter_icons 配置。

自定义命令

自定义命令允许您使用项目特定的命令扩展 Nylo 的 CLI。此功能使您能够自动化重复性任务、实现部署工作流程,或直接将任何自定义功能添加到项目的命令行工具中。

注意: 目前您不能在自定义命令中导入 nylo_framework.dart,请改用 ny_cli.dart。

创建自定义命令

要创建新的自定义命令,您可以使用 make:command 功能:

metro make:command current_time

您可以使用 --category 选项为命令指定类别:

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

这将在 lib/app/commands/current_time.dart 创建一个新的命令文件,结构如下:

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

该命令将自动注册在 lib/app/commands/commands.json 文件中,该文件包含所有已注册命令的列表:

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

运行自定义命令

创建后,您可以使用 Metro 快捷方式或完整的 Dart 命令运行自定义命令:

metro app:current_time

当您不带参数运行 metro 时,您将在菜单中的 "Custom Commands" 部分下看到您的自定义命令:

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

要显示命令的帮助信息,请使用 --help-h 标志:

metro project:install_firebase --help

为命令添加选项

选项允许您的命令接受用户的额外输入。您可以在 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;
}

然后在命令的 handle 方法中访问选项值:

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

  // Command implementation...
}

使用示例:

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

为命令添加标志

标志是可以开关的布尔选项。使用 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;
}

然后在命令的 handle 方法中检查标志状态:

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

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

  // Command implementation...
}

使用示例:

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

辅助方法

NyCustomCommand 基类提供了多个辅助方法来协助常见任务:

打印消息

以下是一些用不同颜色打印消息的方法:

info 以蓝色文本打印信息消息
error 以红色文本打印错误消息
success 以绿色文本打印成功消息
warning 以黄色文本打印警告消息

运行进程

运行进程并在控制台中显示其输出:

addPackage pubspec.yaml 添加包
addPackages pubspec.yaml 添加多个包
runProcess 运行外部进程并在控制台中显示输出
prompt 以文本形式收集用户输入
confirm 提出是/否问题并返回布尔结果
select 展示选项列表并让用户选择一个
multiSelect 允许用户从列表中选择多个选项

网络请求

通过控制台发出网络请求:

api 使用 Nylo API 客户端发出 API 调用

加载动画

在执行函数时显示加载动画:

withSpinner 在执行函数时显示加载动画
createSpinner 创建动画实例以进行手动控制

自定义命令辅助方法

您还可以使用以下辅助方法来管理命令参数:

getString 从命令参数获取字符串值
getBool 从命令参数获取布尔值
getInt 从命令参数获取整数值
sleep 暂停执行指定时长

运行外部进程

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

包管理

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

输出格式化

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

交互式输入方法

NyCustomCommand 基类提供了多种方法来在终端中收集用户输入。这些方法使为您的自定义命令创建交互式命令行界面变得简单。

文本输入

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

向用户显示问题并收集文本响应。

参数:

  • question:要显示的问题或提示
  • defaultValue:如果用户只按回车键时的可选默认值

返回: 用户输入的字符串,或如果未提供输入则返回默认值

示例:

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

确认

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

向用户提出是/否问题并返回布尔结果。

参数:

  • question:要提出的是/否问题
  • defaultValue:默认答案(true 为是,false 为否)

返回: 如果用户回答是则返回 true,回答否则返回 false

示例:

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

单选

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

展示选项列表并让用户选择一个。

参数:

  • question:选择提示
  • options:可用选项列表
  • defaultOption:可选的默认选择

返回: 所选选项的字符串

示例:

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

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

多选

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

允许用户从列表中选择多个选项。

参数:

  • question:选择提示
  • options:可用选项列表

返回: 所选选项的列表

示例:

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

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

API 辅助方法

api 辅助方法简化了从自定义命令发出网络请求的过程。

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

基本使用示例

GET 请求

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

POST 请求

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

PUT 请求

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

DELETE 请求

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

PATCH 请求

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

带查询参数

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

带 Spinner

// Using with spinner for better UI
final data = await withSpinner(
  task: () async {
    final data = await api((request) => request.get('https://api.example.com/config'));
    // Process the data
  },
  message: 'Loading configuration',
);

Spinner 功能

Spinner 在自定义命令中的长时间运行操作期间提供视觉反馈。它们在命令执行异步任务时显示带有消息的动画指示器,通过显示进度和状态来增强用户体验。

使用 spinner

withSpinner 方法允许您使用 spinner 动画包装异步任务,该动画在任务开始时自动启动,在完成或失败时停止:

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

参数:

  • task:要执行的异步函数
  • message:spinner 运行时显示的文本
  • successMessage:成功完成时显示的可选消息
  • errorMessage:任务失败时显示的可选消息

返回: 任务函数的结果

示例:

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

手动 Spinner 控制

对于需要手动控制 spinner 状态的更复杂场景,您可以创建 spinner 实例:

ConsoleSpinner createSpinner(String message)

参数:

  • message:spinner 运行时显示的文本

返回: 一个您可以手动控制的 ConsoleSpinner 实例

手动控制示例:

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

示例

简单任务带 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',
  );
}

多个连续操作

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

手动控制的复杂工作流

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

在自定义命令中使用 spinner 可在长时间运行操作期间为用户提供清晰的视觉反馈,创造更精致和专业的命令行体验。

从选项获取字符串值

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

参数:

  • name:要获取的选项名称
  • defaultValue:如果未提供选项时的可选默认值

返回: 选项的字符串值

示例:

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

从选项获取布尔值

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

参数:

  • name:要获取的选项名称
  • defaultValue:如果未提供选项时的可选默认值

返回: 选项的布尔值

示例:

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

从选项获取整数值

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

参数:

  • name:要获取的选项名称
  • defaultValue:如果未提供选项时的可选默认值

返回: 选项的整数值

示例:

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

暂停指定时长

void sleep(int seconds)

参数:

  • seconds:暂停的秒数

返回:

示例:

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

输出格式化

除了基本的 infoerrorsuccesswarning 方法外,NyCustomCommand 还提供额外的输出辅助方法:

@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
  }
}
方法 描述
line(String message) 打印无颜色的纯文本
newLine([int count = 1]) 打印空行
comment(String message) 打印淡化/灰色文本
alert(String message) 打印醒目的警告框
ask(String question, {String defaultValue}) prompt 的别名
promptSecret(String question) 敏感数据的隐藏输入
abort([String? message, int exitCode = 1]) 以错误退出命令

文件系统辅助方法

NyCustomCommand 包含内置的文件系统辅助方法,因此您无需手动导入 dart:io 即可进行常见操作。

读写文件

@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');
}
方法 描述
fileExists(String path) 如果文件存在则返回 true
directoryExists(String path) 如果目录存在则返回 true
readFile(String path) 以字符串形式读取文件(异步)
readFileSync(String path) 以字符串形式读取文件(同步)
writeFile(String path, String content) 写入文件内容(异步)
writeFileSync(String path, String content) 写入文件内容(同步)
appendFile(String path, String content) 追加文件内容
ensureDirectory(String path) 如果不存在则创建目录
deleteFile(String path) 删除文件
copyFile(String source, String destination) 复制文件

JSON 和 YAML 辅助方法

使用内置辅助方法读写 JSON 和 YAML 文件。

@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']}');
}
方法 描述
readJson(String path) 将 JSON 文件读取为 Map<String, dynamic>
readJsonArray(String path) 将 JSON 文件读取为 List<dynamic>
writeJson(String path, dynamic data, {bool pretty = true}) 将数据写入为 JSON
appendToJsonArray(String path, Map item, {String? uniqueKey}) 追加到 JSON 数组文件
readYaml(String path) 将 YAML 文件读取为 Map<String, dynamic>

大小写转换辅助方法

无需导入 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
}
方法 输出格式 示例
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

项目路径辅助方法

标准 Nylo Website 项目目录的 getter。这些返回相对于项目根目录的路径。

@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');
}
属性 路径
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) 解析项目内的相对路径

平台辅助方法

检查平台并访问环境变量。

@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');
}
属性 / 方法 描述
isWindows 在 Windows 上运行时为 true
isMacOS 在 macOS 上运行时为 true
isLinux 在 Linux 上运行时为 true
workingDirectory 当前工作目录路径
env(String key, [String defaultValue = '']) 读取系统环境变量

Dart 和 Flutter 命令

将常见的 Dart 和 Flutter CLI 命令作为辅助方法运行。每个方法返回进程退出代码。

@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
}
方法 描述
dartFormat(String path) 对文件或目录运行 dart format
dartAnalyze([String? path]) 运行 dart analyze
flutterPubGet() 运行 flutter pub get
flutterClean() 运行 flutter clean
flutterBuild(String target, {List<String> args}) 运行 flutter build <target>
flutterTest([String? path]) 运行 flutter test

Dart 文件操作

用于以编程方式编辑 Dart 文件的辅助方法,在构建脚手架工具时很有用。

@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'),
  );
}
方法 描述
addImport(String filePath, String importStatement) 向 Dart 文件添加导入(如果已存在则跳过)
insertBeforeClosingBrace(String filePath, String code) 在文件最后一个 } 之前插入代码
fileContains(String filePath, String identifier) 检查文件是否包含字符串
fileContainsPattern(String filePath, Pattern pattern) 检查文件是否匹配模式

目录辅助方法

用于处理目录和查找文件的辅助方法。

@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');
}
方法 描述
listDirectory(String path, {bool recursive = false}) 列出目录内容
findFiles(String directory, {String? extension, Pattern? namePattern, bool recursive = true}) 查找匹配条件的文件
deleteDirectory(String path) 递归删除目录
copyDirectory(String source, String destination) 递归复制目录

验证辅助方法

用于代码生成时验证和清理用户输入的辅助方法。

@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'
}
方法 描述
isValidDartIdentifier(String name) 验证 Dart 标识符名称
requireArgument(CommandResult result, {String? message}) 要求非空的第一个参数或中止
cleanClassName(String name, {List<String> removeSuffixes}) 清理并转换为 PascalCase 类名
cleanFileName(String name, {String extension = '.dart'}) 清理并转换为 snake_case 文件名

文件脚手架

使用脚手架系统创建一个或多个带内容的文件。

单个文件

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

多个文件

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

ScaffoldFile 类接受:

属性 类型 描述
path String 要创建的文件路径
content String 文件内容
successMessage String? 成功时显示的消息

任务运行器

运行一系列命名任务并自动输出状态。

基本任务运行器

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

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

CommandTask 类接受:

属性 类型 默认值 描述
name String 必填 输出中显示的任务名称
action Future<void> Function() 必填 要执行的异步函数
stopOnError bool true 如果此任务失败是否停止剩余任务

表格输出

在控制台中显示格式化的 ASCII 表格。

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

输出:

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

进度条

为已知项目数量的操作显示进度条。

手动进度条

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

带进度处理项目

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

同步进度

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

ConsoleProgressBar 类提供:

方法 描述
start() 启动进度条
tick([int amount = 1]) 增加进度
update(int value) 将进度设置为特定值
updateMessage(String newMessage) 更改显示的消息
complete([String? completionMessage]) 以可选消息完成
stop() 停止但不完成
current 当前进度值(getter)
percentage 进度百分比(getter)