命令
简介
命令允许您使用自定义的项目专用工具扩展 Nylo Website 的 CLI。通过继承 NyCustomCommand,您可以自动化重复任务、构建部署工作流、生成代码,或在终端中直接添加任何您需要的功能。
每个自定义命令都可以访问一组丰富的内置辅助工具,包括文件 I/O、JSON/YAML、交互式提示、加载动画、进度条、API 请求等——无需导入额外的包。
注意: 自定义命令在 Flutter 运行时之外运行。您不能在命令中导入
nylo_framework.dart,请改用ny_cli.dart。
创建命令
使用 Metro 或 Dart CLI 创建新命令:
metro make:command current_time
您可以使用 --category 选项为命令指定类别:
metro make:command current_time --category="project"
这将在 lib/app/commands/current_time.dart 创建一个新文件,并将其注册到命令注册表中。
命令结构
每个命令都继承 NyCustomCommand 并实现两个关键方法:
builder()-- 配置选项和标志handle()-- 执行命令逻辑
import 'package:nylo_framework/metro/ny_cli.dart';
void main(arguments) => _CurrentTimeCommand(arguments).run();
/// Current Time Command
///
/// Usage:
/// metro app:current_time
class _CurrentTimeCommand extends NyCustomCommand {
_CurrentTimeCommand(super.arguments);
@override
CommandBuilder builder(CommandBuilder command) {
command.addOption('format', defaultValue: 'HH:mm:ss');
return command;
}
@override
Future<void> handle(CommandResult result) async {
final format = result.getString("format");
info("Current time format: $format");
}
}
运行命令
使用 Metro 或 Dart 运行您的命令:
metro app:current_time
命令名称遵循 category:name 的模式。当您不带参数运行 metro 时,自定义命令会显示在 Custom Commands 部分下:
[Custom Commands]
app:current_time
project:install_firebase
project:deploy
要显示命令的帮助信息:
metro app:current_time --help
命令注册表
所有自定义命令都注册在 lib/app/commands/commands.json 中。当您使用 make:command 时,此文件会自动更新:
[
{
"name": "install_firebase",
"category": "project",
"script": "install_firebase.dart"
},
{
"name": "current_time",
"category": "app",
"script": "current_time.dart"
}
]
每个条目包含:
| 字段 | 描述 |
|---|---|
name |
命令名称(在类别前缀之后使用) |
category |
命令类别(例如 app、project) |
script |
lib/app/commands/ 中的 Dart 文件 |
选项和标志
在 builder() 方法中使用 CommandBuilder 配置命令的选项和标志。
添加选项
选项接受用户提供的值:
@override
CommandBuilder builder(CommandBuilder command) {
command.addOption(
'environment',
abbr: 'e',
help: 'Target deployment environment',
defaultValue: 'development',
allowed: ['development', 'staging', 'production'],
);
return command;
}
用法:
metro project:deploy --environment=production
# or using abbreviation
metro project:deploy -e production
| 参数 | 类型 | 描述 |
|---|---|---|
name |
String |
选项名称 |
abbr |
String? |
单字符缩写 |
help |
String? |
使用 --help 时显示的帮助文本 |
allowed |
List<String>? |
限制为允许的值 |
defaultValue |
String? |
未提供时的默认值 |
添加标志
标志是布尔开关:
@override
CommandBuilder builder(CommandBuilder command) {
command.addFlag(
'verbose',
abbr: 'v',
help: 'Enable verbose output',
defaultValue: false,
);
return command;
}
用法:
metro project:deploy --verbose
metro project:deploy -v
| 参数 | 类型 | 描述 |
|---|---|---|
name |
String |
标志名称 |
abbr |
String? |
单字符缩写 |
help |
String? |
使用 --help 时显示的帮助文本 |
defaultValue |
bool |
默认值(默认:false) |
命令结果
handle() 方法接收一个 CommandResult 对象,该对象提供类型化的访问器用于读取已解析的选项、标志和参数。
@override
Future<void> handle(CommandResult result) async {
// Get a string option
final name = result.getString('name');
// Get a boolean flag
final verbose = result.getBool('verbose');
// Get an integer option
final count = result.getInt('count');
// Generic typed access
final value = result.get<String>('key');
// Built-in flag checks
if (result.hasForceFlag) { /* --force was passed */ }
if (result.hasHelpFlag) { /* --help was passed */ }
// Raw arguments
List<String> allArgs = result.arguments;
List<String> unparsed = result.rest;
}
| 方法 / 属性 | 返回类型 | 描述 |
|---|---|---|
getString(String name, {String? defaultValue}) |
String? |
获取字符串值 |
getBool(String name, {bool? defaultValue}) |
bool? |
获取布尔值 |
getInt(String name, {int? defaultValue}) |
int? |
获取整数值 |
get<T>(String name) |
T? |
获取类型化的值 |
hasForceFlag |
bool |
是否传递了 --force |
hasHelpFlag |
bool |
是否传递了 --help |
arguments |
List<String> |
所有命令行参数 |
rest |
List<String> |
未解析的剩余参数 |
交互式输入
NyCustomCommand 提供了在终端中收集用户输入的方法。
文本输入
// Ask a question with optional default
final name = prompt('What is your project name?', defaultValue: 'my_app');
// ask() is an alias for prompt()
final description = ask('Enter a description:');
| 参数 | 类型 | 描述 |
|---|---|---|
question |
String |
要显示的问题 |
defaultValue |
String |
用户按回车时的默认值(默认:'') |
确认
if (confirm('Would you like to continue?', defaultValue: true)) {
await runProcess('flutter pub get');
} else {
info('Operation canceled');
}
| 参数 | 类型 | 描述 |
|---|---|---|
question |
String |
是/否问题 |
defaultValue |
bool |
默认答案(默认:false) |
单项选择
final environment = select(
'Select deployment environment:',
['development', 'staging', 'production'],
defaultOption: 'development',
);
| 参数 | 类型 | 描述 |
|---|---|---|
question |
String |
提示文本 |
options |
List<String> |
可用选项 |
defaultOption |
String? |
预选选项 |
多项选择
final packages = multiSelect(
'Select packages to install:',
['firebase_auth', 'dio', 'provider', 'shared_preferences'],
);
if (packages.isNotEmpty) {
addPackages(packages);
await runProcess('flutter pub get');
}
用户输入以逗号分隔的编号或 "all"。
隐秘输入
final apiKey = promptSecret('Enter your API key:');
输入内容在终端中不可见。如果不支持回显模式,则回退为可见输入。
输出格式化
用于在控制台中打印样式化输出的方法:
@override
Future<void> handle(CommandResult result) async {
info('Processing files...'); // Blue text
error('Operation failed'); // Red text
success('Deployment complete'); // Green text
warning('Outdated package'); // Yellow text
line('Plain text output'); // No color
comment('Background note'); // Gray text
alert('Important notice'); // Bordered alert box
newLine(); // One blank line
newLine(3); // Three blank lines
// Exit the command with an error
abort('Fatal error occurred'); // Prints red, exits with code 1
}
| 方法 | 描述 |
|---|---|
info(String message) |
打印蓝色文本 |
error(String message) |
打印红色文本 |
success(String message) |
打印绿色文本 |
warning(String message) |
打印黄色文本 |
line(String message) |
打印纯文本(无颜色) |
newLine([int count = 1]) |
打印空行 |
comment(String message) |
打印灰色/弱化文本 |
alert(String message) |
打印带边框的提醒框 |
abort([String? message, int exitCode = 1]) |
以错误退出命令 |
加载动画和进度条
加载动画和进度条在长时间运行的操作中提供视觉反馈。
使用 withSpinner
使用自动加载动画包装异步任务:
final projectFiles = await withSpinner(
task: () async {
await sleep(2);
return ['pubspec.yaml', 'lib/main.dart', 'README.md'];
},
message: 'Analyzing project structure',
successMessage: 'Project analysis complete',
errorMessage: 'Failed to analyze project',
);
info('Found ${projectFiles.length} key files');
| 参数 | 类型 | 描述 |
|---|---|---|
task |
Future<T> Function() |
要执行的异步函数 |
message |
String |
加载动画运行时显示的文本 |
successMessage |
String? |
成功时显示的文本 |
errorMessage |
String? |
失败时显示的文本 |
手动控制加载动画
对于多步骤工作流,创建加载动画并手动控制它:
final spinner = createSpinner('Deploying to production');
spinner.start();
try {
await runProcess('flutter clean', silent: true);
spinner.update('Building release version');
await runProcess('flutter build web --release', silent: true);
spinner.update('Uploading to server');
await runProcess('./deploy.sh', silent: true);
spinner.stop(completionMessage: 'Deployment completed', success: true);
} catch (e) {
spinner.stop(completionMessage: 'Deployment failed: $e', success: false);
}
ConsoleSpinner 方法:
| 方法 | 描述 |
|---|---|
start([String? message]) |
启动加载动画 |
update(String message) |
更改显示的消息 |
stop({String? completionMessage, bool success = true}) |
停止加载动画 |
进度条
手动创建和管理进度条:
final progress = progressBar(100, message: 'Processing files');
progress.start();
for (int i = 0; i < 100; i++) {
await Future.delayed(Duration(milliseconds: 50));
progress.tick();
}
progress.complete('All files processed');
ConsoleProgressBar 方法:
| 方法 / 属性 | 描述 |
|---|---|
start() |
启动进度条 |
tick([int amount = 1]) |
增加进度 |
update(int value) |
将进度设置为特定值 |
updateMessage(String newMessage) |
更改显示的消息 |
complete([String? completionMessage]) |
完成并显示可选消息 |
stop() |
停止但不完成 |
current |
当前进度值(getter) |
percentage |
进度百分比 0-100(getter) |
带进度处理项目
处理项目列表并自动跟踪进度:
// Async processing
final results = await withProgress<File, String>(
items: findFiles('lib/', extension: '.dart'),
process: (file, index) async {
return file.path;
},
message: 'Analyzing Dart files',
completionMessage: 'Analysis complete',
);
// Synchronous processing
final upperItems = withProgressSync<String, String>(
items: ['a', 'b', 'c', 'd', 'e'],
process: (item, index) => item.toUpperCase(),
message: 'Converting items',
);
API 辅助工具
api 辅助工具提供了对 Dio 的简化封装,用于发起 HTTP 请求:
// GET request
final userData = await api((request) =>
request.get('https://api.example.com/users/1')
);
// POST request
final result = await api((request) =>
request.post(
'https://api.example.com/items',
data: {'name': 'New Item', 'price': 19.99},
)
);
// PUT request
final updateResult = await api((request) =>
request.put(
'https://api.example.com/items/42',
data: {'name': 'Updated Item', 'price': 29.99},
)
);
// DELETE request
final deleteResult = await api((request) =>
request.delete('https://api.example.com/items/42')
);
// PATCH request
final patchResult = await api((request) =>
request.patch(
'https://api.example.com/items/42',
data: {'price': 24.99},
)
);
// With query parameters
final searchResults = await api((request) =>
request.get(
'https://api.example.com/search',
queryParameters: {'q': 'keyword', 'limit': 10},
)
);
结合 withSpinner 使用可以获得更好的用户体验:
final data = await withSpinner(
task: () => api((request) =>
request.get('https://api.example.com/config')
),
message: 'Loading configuration',
);
ApiService 支持 get、post、put、delete 和 patch 方法,每个方法都接受可选的 queryParameters、data、options 和 cancelToken。
文件系统辅助工具
内置的文件系统辅助工具,无需导入 dart:io:
// Check existence
if (fileExists('lib/config/app.dart')) { /* ... */ }
if (directoryExists('lib/app/models')) { /* ... */ }
// Read files
String content = await readFile('pubspec.yaml');
String contentSync = readFileSync('pubspec.yaml');
// Write files
await writeFile('lib/generated/output.dart', 'class Output {}');
writeFileSync('lib/generated/output.dart', 'class Output {}');
// Append to a file
await appendFile('log.txt', 'New log entry\n');
// Ensure a directory exists
await ensureDirectory('lib/generated');
// Delete and copy files
await deleteFile('lib/generated/output.dart');
await copyFile('lib/config/app.dart', 'lib/config/app.bak.dart');
| 方法 | 描述 |
|---|---|
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 文件:
// Read JSON as Map
Map<String, dynamic> config = await readJson('config.json');
// Read JSON as List
List<dynamic> items = await readJsonArray('lib/app/commands/commands.json');
// Write JSON (pretty printed by default)
await writeJson('output.json', {'name': 'MyApp', 'version': '1.0.0'});
// Write compact JSON
await writeJson('output.json', data, pretty: false);
// Append to a JSON array file (with duplicate prevention)
await appendToJsonArray(
'lib/app/commands/commands.json',
{'name': 'my_command', 'category': 'app', 'script': 'my_command.dart'},
uniqueKey: 'name',
);
// Read YAML as Map
Map<String, dynamic> pubspec = await readYaml('pubspec.yaml');
info('Project: ${pubspec['name']}');
| 方法 | 描述 |
|---|---|
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> |
Dart 文件操作
用于以编程方式编辑 Dart 源文件的辅助工具——在构建脚手架工具时非常有用:
// Add an import (skips if already present)
await addImport(
'lib/bootstrap/providers.dart',
"import '/app/providers/firebase_provider.dart';",
);
// Insert code before the last closing brace
await insertBeforeClosingBrace(
'lib/bootstrap/providers.dart',
' FirebaseProvider(),',
);
// Check if a file contains a string
bool hasImport = await fileContains(
'lib/bootstrap/providers.dart',
'firebase_provider',
);
// Check if a file matches a regex pattern
bool hasClass = await fileContainsPattern(
'lib/app/models/user.dart',
RegExp(r'class User'),
);
| 方法 | 描述 |
|---|---|
addImport(String filePath, String importStatement) |
向 Dart 文件添加导入(如已存在则跳过) |
insertBeforeClosingBrace(String filePath, String code) |
在文件最后一个 } 之前插入代码 |
fileContains(String filePath, String identifier) |
检查文件是否包含字符串 |
fileContainsPattern(String filePath, Pattern pattern) |
检查文件是否匹配模式 |
目录辅助工具
用于处理目录和查找文件的辅助工具:
// List directory contents
var entities = listDirectory('lib/app/models');
var allEntities = listDirectory('lib/', recursive: true);
// Find files by extension
List<File> dartFiles = findFiles(
'lib/app/models',
extension: '.dart',
recursive: true,
);
// Find files by name pattern
List<File> testFiles = findFiles(
'test/',
namePattern: RegExp(r'_test\.dart$'),
);
// Delete a directory recursively
await deleteDirectory('build/');
// Copy a directory recursively
await copyDirectory('lib/templates', 'lib/generated');
| 方法 | 描述 |
|---|---|
listDirectory(String path, {bool recursive = false}) |
列出目录内容 |
findFiles(String directory, {String? extension, Pattern? namePattern, bool recursive = true}) |
查找匹配条件的文件 |
deleteDirectory(String path) |
递归删除目录 |
copyDirectory(String source, String destination) |
递归复制目录 |
大小写转换辅助工具
在命名约定之间转换字符串,无需导入 recase 包:
String input = 'user profile page';
snakeCase(input); // user_profile_page
camelCase(input); // userProfilePage
pascalCase(input); // UserProfilePage
titleCase(input); // User Profile Page
kebabCase(input); // user-profile-page
constantCase(input); // USER_PROFILE_PAGE
| 方法 | 输出格式 | 示例 |
|---|---|---|
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,返回相对于项目根目录的路径:
info(modelsPath); // lib/app/models
info(controllersPath); // lib/app/controllers
info(widgetsPath); // lib/resources/widgets
info(pagesPath); // lib/resources/pages
info(commandsPath); // lib/app/commands
info(configPath); // lib/config
info(providersPath); // lib/app/providers
info(eventsPath); // lib/app/events
info(networkingPath); // lib/app/networking
info(themesPath); // lib/resources/themes
// Build a custom path
String customPath = projectPath('app/services/auth_service.dart');
// Returns: lib/app/services/auth_service.dart
| 属性 | 路径 |
|---|---|
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) |
解析项目内的相对路径 |
平台辅助工具
检查平台并访问环境变量:
if (isWindows) {
info('Running on Windows');
} else if (isMacOS) {
info('Running on macOS');
} else if (isLinux) {
info('Running on Linux');
}
info('Working in: $workingDirectory');
String home = env('HOME', '/default/path');
| 属性 / 方法 | 描述 |
|---|---|
isWindows |
如果在 Windows 上运行则为 true |
isMacOS |
如果在 macOS 上运行则为 true |
isLinux |
如果在 Linux 上运行则为 true |
workingDirectory |
当前工作目录路径 |
env(String key, [String defaultValue = '']) |
读取系统环境变量 |
Dart 和 Flutter 命令
将常用的 Dart 和 Flutter CLI 命令作为辅助方法运行。每个方法返回进程退出码:
// 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/');
| 方法 | 描述 |
|---|---|
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 |
验证辅助工具
用于验证和清理用户输入以进行代码生成的辅助工具:
// Validate a Dart identifier
if (!isValidDartIdentifier('MyClass')) {
error('Invalid Dart identifier');
}
// Require a non-empty first argument (aborts if missing)
String name = requireArgument(result, message: 'Please provide a name');
// Clean a class name (PascalCase, remove suffixes)
String className = cleanClassName('user_model', removeSuffixes: ['_model']);
// Returns: 'User'
// Clean a file name (snake_case with extension)
String fileName = cleanFileName('UserModel', extension: '.dart');
// Returns: 'user_model.dart'
| 方法 | 描述 |
|---|---|
isValidDartIdentifier(String name) |
验证 Dart 标识符名称 |
requireArgument(CommandResult result, {String? message}) |
要求非空的第一个参数,否则中止 |
cleanClassName(String name, {List<String> removeSuffixes}) |
清理并将类名转为 PascalCase |
cleanFileName(String name, {String extension = '.dart'}) |
清理并将文件名转为 snake_case |
文件脚手架
使用脚手架系统创建一个或多个带内容的文件。
单个文件
await scaffold(
path: 'lib/app/services/auth_service.dart',
content: '''
class AuthService {
Future<bool> login(String email, String password) async {
return false;
}
}
''',
force: false,
successMessage: 'AuthService created',
);
| 参数 | 类型 | 描述 |
|---|---|---|
path |
String |
要创建的文件路径 |
content |
String |
文件内容 |
force |
bool |
如果存在则覆盖(默认:false) |
successMessage |
String? |
成功时显示的消息 |
多个文件
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? |
成功时显示的消息 |
任务运行器
运行一系列命名任务并自动输出状态。
基本任务运行器
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,
),
]);
带加载动画的任务运行器
await runTasksWithSpinner([
CommandTask(
'Preparing release',
() async {
await flutterClean();
await flutterPubGet();
},
),
CommandTask(
'Building APK',
() => flutterBuild('apk', args: ['--release']),
),
]);
CommandTask 类:
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
name |
String |
必填 | 输出中显示的任务名称 |
action |
Future<void> Function() |
必填 | 要执行的异步函数 |
stopOnError |
bool |
true |
失败时是否停止剩余任务 |
表格输出
在控制台中显示格式化的 ASCII 表格:
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 │
└─────────────────┴─────────┴───────────┘
示例
当前时间命令
一个显示当前时间的简单命令:
import 'package:nylo_framework/metro/ny_cli.dart';
void main(arguments) => _CurrentTimeCommand(arguments).run();
class _CurrentTimeCommand extends NyCustomCommand {
_CurrentTimeCommand(super.arguments);
@override
CommandBuilder builder(CommandBuilder command) {
command.addOption('format', defaultValue: 'HH:mm:ss');
return command;
}
@override
Future<void> handle(CommandResult result) async {
final format = result.getString("format");
final now = DateTime.now();
info("The current time is ${now.toIso8601String()}");
info("Requested format: $format");
}
}
下载字体命令
一个将 Google Fonts 下载并安装到项目中的命令:
import 'package:nylo_framework/metro/ny_cli.dart';
void main(arguments) => _DownloadFontsCommand(arguments).run();
class _DownloadFontsCommand extends NyCustomCommand {
_DownloadFontsCommand(super.arguments);
@override
CommandBuilder builder(CommandBuilder command) {
command.addOption('font', abbr: 'f', help: 'Font family name');
command.addFlag('verbose', abbr: 'v', defaultValue: false);
return command;
}
@override
Future<void> handle(CommandResult result) async {
final fontName = result.getString('font') ??
prompt('Enter font family name:', defaultValue: 'Roboto');
final verbose = result.getBool('verbose') ?? false;
await withSpinner(
task: () async {
await ensureDirectory('assets/fonts');
final fontData = await api((request) =>
request.get('https://fonts.google.com/download?family=$fontName')
);
if (fontData != null) {
await writeFile('assets/fonts/$fontName.ttf', fontData.toString());
}
},
message: 'Downloading $fontName font',
successMessage: '$fontName font installed',
errorMessage: 'Failed to download $fontName',
);
if (verbose) {
info('Font saved to: assets/fonts/$fontName.ttf');
}
}
}
部署流水线命令
一个使用任务运行器执行完整部署流水线的命令:
import 'package:nylo_framework/metro/ny_cli.dart';
void main(arguments) => _DeployCommand(arguments).run();
class _DeployCommand extends NyCustomCommand {
_DeployCommand(super.arguments);
@override
CommandBuilder builder(CommandBuilder command) {
command.addOption(
'environment',
abbr: 'e',
defaultValue: 'development',
allowed: ['development', 'staging', 'production'],
);
command.addFlag('skip-tests', defaultValue: false);
return command;
}
@override
Future<void> handle(CommandResult result) async {
final env = result.getString('environment') ?? 'development';
final skipTests = result.getBool('skip-tests') ?? false;
alert('Deploying to $env');
newLine();
if (env == 'production') {
if (!confirm('Are you sure you want to deploy to production?')) {
abort('Deployment canceled');
}
}
final tasks = <CommandTask>[
CommandTask('Clean project', () => flutterClean()),
CommandTask('Fetch dependencies', () => flutterPubGet()),
];
if (!skipTests) {
tasks.add(CommandTask(
'Run tests',
() => flutterTest(),
stopOnError: true,
));
}
tasks.add(CommandTask(
'Build for web',
() => flutterBuild('web', args: ['--release']),
));
await runTasksWithSpinner(tasks);
newLine();
success('Deployment to $env completed');
table(
['Step', 'Status'],
tasks.map((t) => [t.name, 'Done']).toList(),
);
}
}