Создайте собственный инструмент командной строки с Dart для проекта Flutter
FlutterPulseЭта статья переведена специально для канала FlutterPulse. В этом канале вы найдёте много интересных вещей, связанных с Flutter. Не забывайте подписываться! 🚀

Введение
Введение
Вы когда-нибудь думали о создании собственного инструмента CLI для вашего проекта Flutter?
Я думаю, у каждого есть своя архитектура проекта или используется какая-то общая архитектура. Если вы используете GetX то вы можете использовать get_cli для генерации архитектуры, это может помочь вам сократить работу и сэкономить время.
Например, структура одного из моих проектов на GetX приведена ниже

Проект на основе GetX
Для этого проекта есть такая же структура с элементами страниц, и мне нужно создавать каждый файл вручную, если я хочу добавить новую страницу.
Поэтому я хочу создать инструмент CLI чтобы помочь в этом!
Создание консольного приложения на Dart
Используйте следующую команду для создания консольного проекта и назовите его CLI
dart create -t console-full cli
Это создаст следующие файлы в папке

Структура приведена ниже

Попробуйте запустить его
dart run bin/cli.dart
Вы получите следующий результат
Привет, мир: 42!
И вы можете скомпилировать это в исполняемый файл
dart compile exe bin/cli.dart
Хорошо, это основа для создания консольного приложения, но нам нужно больше. Я покажу вам, как сгенерировать простой шаблон, как я упоминал выше.
DCli
DCli — это консольный SDK для Dart. Используйте консольный SDK DCli для создания кроссплатформенных приложений командной строки (CLI) и скриптов с использованием языка программирования Dart. Консольный SDK DCli (произносится как d-kleye) включает инструменты командной строки и обширный API для создания приложений CLI.
Это мощный SDK для создания консольных приложений.
DCli имеет следующие цели:
- сделать создание приложений CLI таким же простым, как прогулка.
- полностью использовать выразительность Dart.
- работает плавно с основными библиотеками Dart.
- предоставляет кроссплатформенный API для Windows, OSx и Linux.
- вызывать любое приложение CLI.
- делает отладку приложений CLI простой.
- генерация сообщений об ошибках, которые облегчают решение проблем.
- предоставляет качественную документацию и примеры.
Вы можете установить его с помощью следующей команды
dart pub global activate dcli dcli install
Вы можете найти подробное описание использования на их официальном сайте. Они предоставили всестороннюю документацию, и я покажу вам, как создать и сгенерировать коды в этой статье 😀.
Создание шаблона
Для примера выше нам нужно создать следующую структуру папок

Таким образом, мы можем создать шаблоны файлов и папок в папке lib

Для простого примера содержимое controller.template следующее
class {{className}}Controller extends GetxController with BaseControllerMixin {
@override
String get builderId => '{{pageName}}';
{{className}}Controller();
@override
void onInit() {
super.onInit();
}
/// Следует ли отслеживать события жизненного цикла
@override
bool get listenLifecycleEvent => true;
/// Когда listenLifecycle Event установлен в true, будут вызваны следующие методы жизненного цикла
@override
void onDetached() {
log('onDetached');
}
@override
void onHidden() {
log('onHidden');
}
@override
void onInactive() {
log('onInactive');
}
@override
void onPaused() {
log('onPaused');
}
@override
void onResumed() {
log('onResumed');
}
}
Обратите внимание, что это только для демонстрации, поэтому код неполный, и в этом шаблоне нет необходимых библиотек для импорта.
Файл view.template
class {{className}}Page extends GetView<{{className}}Controller> {
const {{className}}Page({super.key});
}
Файл index.template
library {{pageName}};
export 'controller.dart';
export 'view.dart';
Как видно, в шаблоне есть некоторые переменные, и мы будем заменять их динамически с помощью команды CLI.
Реализация функций CLI
Добавьте зависимость dcli в pubspec.yaml
dependencies: dcli: ^7.0.3 #use the latest version
Для общего инструмента CLI должны поддерживаться такие аргументы, как -help или -version чтобы пользователь знал, как его использовать и какая текущая версия.
Поэтому сначала нужно обработать эти аргументы:
final parser =
ArgParser()
..addFlag(
'help',
abbr: 'h',
help: 'Show usage information.',
negatable: false,
)
..addFlag(
'version',
abbr: 'v',
help: 'Show version information.',
negatable: false,
);
final results = parser.parse(arguments);
if (results['help']) {
printUsage(parser);
exit(0);
}
if (results['version']) {
printVersion();
exit(0);
}
Я хочу использовать этот CLI для создания новой страницы по команде на основе шаблонов файлов
cli create page:home
Предположим, после выполнения вышеуказанной команды будут созданы следующие файлы и папки

Теперь нужно обработать аргументы create page:home
final command = arguments.sublist(1).join(' ');
if (!command.startsWith('page:')) {
print('Invalid command format. Expected: create page:<pagename>');
printUsage(parser);
exit(1);
}
final pageName = command.split(':')[1];
createPage(pageName);
Создайте папку pages по команде и перезапишите, если она существует
final dirPath = path.join(Directory.current.path, 'pages', pageName);
// Delete directory if it exists
if (exists(dirPath)) {
find(
'*',
workingDirectory: dirPath,
recursive: true,
types: [Find.file],
).forEach(delete);
deleteDir(dirPath);
print('Deleted existing directory $dirPath');
}
// Create directory
createDir(dirPath, recursive: true);
print('Created directory $dirPath');
Загрузите шаблоны файлов и динамически замените переменные
// Преобразование в PascalCase для имен классов
final className = toPascalCase(pageName);
// Загрузка шаблонов из пакета
final templateDir = 'lib/templates/page';
final controllerTemplatePath = path.join(templateDir, 'controller.template');
final viewTemplatePath = path.join(templateDir, 'view.template');
final indexTemplatePath = path.join(templateDir, 'index.template');
final controllerTemplate = loadAsset(controllerTemplatePath);
final viewTemplate = loadAsset(viewTemplatePath);
final indexTemplate = loadAsset(indexTemplatePath);
if (controllerTemplate == null ||
viewTemplate == null ||
indexTemplate == null) {
print(
'Один или несколько файлов шаблонов отсутствуют в директории lib/templates/page.',
);
exit(1);
}
// Замена заполнителей в шаблонах
final controllerContent = controllerTemplate
.replaceAll('{{className}}', className)
.replaceAll('{{pageName}}', pageName);
final viewContent = viewTemplate.replaceAll('{{className}}', className);
final indexContent = indexTemplate.replaceAll('{{pageName}}', pageName);
// Создание или перезапись controller.dart
final controllerFilePath = path.join(dirPath, 'controller.dart');
File(controllerFilePath).writeAsStringSync(controllerContent);
print('Создан или перезаписан файл $controllerFilePath');
// Создание или перезапись view.dart
final viewFilePath = path.join(dirPath, 'view.dart');
File(viewFilePath).writeAsStringSync(viewContent);
print('Создан или перезаписан файл $viewFilePath');
// Создание или перезапись index.dart
final indexPath = path.join(dirPath, 'index.dart');
File(indexPath).writeAsStringSync(indexContent);
print('Создан или перезаписан файл $indexPath');
Функция ниже загрузит файлы шаблонов из базовой папки проекта
String? loadAsset(String assetPath) {
final scriptFile = Platform.script.toFilePath();
final scriptDir = path.dirname(scriptFile);
final packageRoot = path.join(
scriptDir,
'..',
); // Предполагается, что bin находится на один уровень ниже корня
final filePath = path.join(packageRoot, assetPath);
// final filePath = path.join(scriptDir, assetPath);
try {
final content = File(filePath).readAsStringSync();
return content;
} catch (e) {
print('Не удалось загрузить ресурс: $filePath');
return null;
}
}
Я не показал весь код выше, а только основной. Вы можете найти полный проект здесь.
Тестирование CLI
После завершения вы можете запустить CLI как консольное приложение ниже
dart bin/cli.dart -v
Вы должны получить следующий результат
cli version v1.0.0
И попробуйте создать элемент страницы
dart bin/cli.dart create page:home
Будет сгенерировано 3 файла на основе файлов шаблонов
pages/home.dart файл
class HomeController extends GetxController with BaseControllerMixin {
@override
String get builderId => 'home';
HomeController();
@override
void onInit() {
super.onInit();
}
/// Следить за событиями жизненного цикла
@override
bool get listenLifecycleEvent => true;
/// Когда listenLifecycle Event установлен в true, будут вызваны следующие методы жизненного цикла
@override
void onDetached() {
log('onDetached');
}
@override
void onHidden() {
log('onHidden');
}
@override
void onInactive() {
log('onInactive');
}
@override
void onPaused() {
log('onPaused');
}
@override
void onResumed() {
log('onResumed');
}
}
pages/view.dart файл
class HomePage extends GetView<HomeController> {
const HomePage({super.key});
}
pages/index.dart файл
library home; export 'controller.dart'; export 'view.dart';
Отлично, мы почти закончили!
Активация глобальной команды
Потому что есть шаблонные файлы и папки с CLI , поэтому если вы скопируете исполняемый файл в папку вашего проекта и также нужно скопировать шаблонные файлы, это не то, что нам нужно. Я думаю, вы захотите использовать ваш инструмент CLI где угодно, верно?
Хорошо, давайте сделаем это!
Нам нужно активировать проект CLI как глобальный инструмент, а также нужно включить шаблонные файлы, чтобы мы могли обновить файл pubspec.yaml ниже
executables: cli: cli # Include templates in the package include: - lib/templates/page/
И выполните команду ниже в корневой папке проекта, чтобы активировать её как глобальную команду
dart pub global activate --source path .
Вы можете использовать команду ниже, чтобы увидеть, добавлено ли это в глобальную
dart pub global list
Если вы хотите удалить его, просто выполните ниже
dart pub global deactivate cli
Тестирование глобальной версии
Теперь вы можете использовать CLI где угодно
cli create page:home
Если у вас возникнут следующие проблемы
Failed to load asset: /Volumes/cli/.dart_tool/pub/bin/cli/../lib/templates/page/controller.template Failed to load asset: /Volumes/cli/.dart_tool/pub/bin/cli/../lib/templates/page/view.template Failed to load asset: /Volumes/cli/.dart_tool/pub/bin/cli/../lib/templates/page/index.template
Это происходит потому, что приложение не может получить текущую папку, оно скопирует её в папку .dart_tool после публикации в глобальную, решение заключается в том, что вы можете жестко закодировать папку проекта в loadAsset
final packageRoot = '/Volumes/cli/'; //hardcode to your CLI project folder final filePath = path.join(packageRoot, assetPath); ...
Обновление новой версии
В конце, если вы отредактируете код CLI и захотите опубликовать его снова, вы обнаружите, что это не работает, вам нужно следовать следующим шагам:
- Удалите папку
.dart_toolв корневой папке вашего проекта. - Получите зависимости проекта снова. (сохраните файл
pubspec.yamlснова) - Не нужно запускать команду активации глобально, но нужно запустить ниже для активации снова:
cli -h
Вы можете найти полный проект ниже
Заключение
На самом деле, инструмент CLI не только для проектов Flutter, вы можете создавать любые инструменты, которые хотите, чтобы сэкономить время на повторяющихся задачах.
Инструмент DCli также очень мощный и позволяет делать много вещей, поэтому я рекомендую вам изучить и попробовать его, если вы хотите создать CLI 😁