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!');
}
输出格式化
除了基本的 info、error、success 和 warning 方法外,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) |