Create your own CLI tool with Dart for the Flutter project
FlutterPulseThis article was translated specially for the channel FlutterPulseYou'll find lots of interesting things related to Flutter on this channel. Don't hesitate to subscribe!🚀

Introduction
Introduction
Have you ever thought about creating your own CLI tool for your Flutter project?
I think maybe everyone has their own project architecture or uses some common architecture. If you are using GetX then you can use get_cli to generate the architecture, it can help you reduce your work and save time.
For example, one of my GetX project structure as below

For this project, there is the same structure with page items, I need to create each file manually if I want to add a new page.
So, I want to create a CLI tool to help it!
Create a console app with Dart
Use the below command to create a console project and name to CLI
dart create -t console-full cli
It will create the following files in the folder

The structure as below

Try to run it
dart run bin/cli.dart
You will get the following result
Hello world: 42!
And you can compile it to an executable file
dart compile exe bin/cli.dart
Ok, this is the base for creating a console app, but we need more. I will show you how to generate a simple template as I mentioned above.
DCli
DCli is the console SDK for Dart. Use the DCli console SDK to build cross platform, command line (CLI) applications and scripts using the Dart programming language. The DCli (pronounced d-kleye) console SDK includes command line tools and an extensive API for building CLI apps.
This is a powerful SDK for helping to create a console app.
DCli has the following aims:
- make building CLI apps as easy as walking.
- fully utilise the expressiveness of Dart.
- works seamlessly with the core Dart libraries.
- provide a cross platform API for Windows, OSx and Linux.
- call any CLI app.
- make debugging CLI apps easy.
- generate error messages that make it easy to resolve problems.
- provide quality documentation and examples.
You can install it as below command
dart pub global activate dcli
dcli install
You can find the detailed usage on their official website. They provided comprehensive documentation, and I will show you how to create and generate the codes in this article 😀.
Create Template
For the above example, we need to create the following folder structure

So we can create the template files and folders in lib folder

For a simple example, the controller.template content as below
class {{className}}Controller extends GetxController with BaseControllerMixin {
@override
String get builderId => '{{pageName}}';
{{className}}Controller();
@override
void onInit() {
super.onInit();
}
/// Whether to monitor lifecycle events
@override
bool get listenLifecycleEvent => true;
/// When listenLifecycle Event is set to true, the following lifecycle methods will be called
@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');
}
}Please note that this is only for demo, so the code is not complete, and there are no required libraries to be imported in this template.
The view.template file
class {{className}}Page extends GetView<{{className}}Controller> {
const {{className}}Page({super.key});
}The index.template file
library {{pageName}};
export 'controller.dart';
export 'view.dart';As you can see, there are some variables in the template, and we will replace them dynamically with the CLI command.
Implement the CLI functions
Add the dcli dependence into pubspec.yaml
dependencies:
dcli: ^7.0.3 #use the latest version
For a common CLI tool, should be supported, such as -help or -version arguments to let the user know how to use and what the current version.
So we need to handle these arguments first:
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);
}
I want to use this CLI to create a new page as below the command based on template files
cli create page:home
Suppose after running the above command, it will generate the following files and folders

Now we need to handle the create page:homearguments
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); Create the pages folder by command and overwrite if it exists
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');
Load template files and dynamically replace the variables
// PascalCase conversion for class names
final className = toPascalCase(pageName);
// Load templates from the package
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(
'One or more template files are missing in the lib/templates/page directory.',
);
exit(1);
}
// Replace placeholders in templates
final controllerContent = controllerTemplate
.replaceAll('{{className}}', className)
.replaceAll('{{pageName}}', pageName);
final viewContent = viewTemplate.replaceAll('{{className}}', className);
final indexContent = indexTemplate.replaceAll('{{pageName}}', pageName);
// Create or overwrite controller.dart
final controllerFilePath = path.join(dirPath, 'controller.dart');
File(controllerFilePath).writeAsStringSync(controllerContent);
print('Created or overwritten file $controllerFilePath');
// Create or overwrite view.dart
final viewFilePath = path.join(dirPath, 'view.dart');
File(viewFilePath).writeAsStringSync(viewContent);
print('Created or overwritten file $viewFilePath');
// Create or overwrite index.dart
final indexPath = path.join(dirPath, 'index.dart');
File(indexPath).writeAsStringSync(indexContent);
print('Created or overwritten file $indexPath');
The below function will load the template files base project folder
String? loadAsset(String assetPath) {
final scriptFile = Platform.script.toFilePath();
final scriptDir = path.dirname(scriptFile);
final packageRoot = path.join(
scriptDir,
'..',
); // Assuming bin is one level below the root
final filePath = path.join(packageRoot, assetPath);
// final filePath = path.join(scriptDir, assetPath);
try {
final content = File(filePath).readAsStringSync();
return content;
} catch (e) {
print('Failed to load asset: $filePath');
return null;
}
}I didn't show all of the code above, but just the core code. You can find the complete project here.
Test the CLI
After done, you can run the CLI as a console app below
dart bin/cli.dart -v
You should get below result
cli version v1.0.0
And try to create the page item
dart bin/cli.dart create page:home
It will generate 3 files based on the template files
pages/home.dartfile
class HomeController extends GetxController with BaseControllerMixin {
@override
String get builderId => 'home';
HomeController();
@override
void onInit() {
super.onInit();
}
/// Whether to monitor lifecycle events
@override
bool get listenLifecycleEvent => true;
/// When listenLifecycle Event is set to true, the following lifecycle methods will be called
@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.dartfile
class HomePage extends GetView<HomeController> {
const HomePage({super.key});
}pages/index.dart file
library home;
export 'controller.dart';
export 'view.dart';
That's great, we're almost done!
Activate to Global Command
Because there are template files and folders with the CLI , so if you copy the executable file to your project folder and you also need to copy the template files, this is not what we want. I think you will want to use your CLI tool anywhere, right?
Ok, let's do it!
We need to activate the CLI project as a global tool, and also need to include the template files, so that we can update the pubspec.yaml file below
executables:
cli: cli
# Include templates in the package
include:
- lib/templates/page/
And run the below command in the project root folder to activate it to global command
dart pub global activate --source path .
You can use the command below to see whether it has been added to the global
dart pub global list
If you want to remove it, just run the below
dart pub global deactivate cli
Test the Global Version
Now you can use the CLI anywhere
cli create page:home
If you encounter the following problems
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
That's because the app can't get the current folder, it will copy to the .dart_tool folder after publishing to the global, the solution is you can hardcode the project folder in loadAsset
final packageRoot = '/Volumes/cli/'; //hardcode to your CLI project folder
final filePath = path.join(packageRoot, assetPath);
...
Update the New Version
At the end, if you edit the CLI code want to publish again, you will find that's not working, you need to follow below steps:
- Delete the
.dart_toolfolder in your project root. - Get the project dependencies again. (save the
pubspec.yamlfile again) - Don't need to run the activate global command, but need to run the below for activation again:
cli -h
You can find the complete project below
GitHub - coderblog-winson/cli_demo: The demo for how to create CLI tool by dartThe demo for how to create CLI tool by dart. Contribute to coderblog-winson/cli_demo development by creating an account…
github.com
Conclusion
Actually, the CLI tool not only for Flutter project, you can create any tools that you want, which can save your time for duplicate tasks.
The DCli is also very powerful and lets you do a lot of things, so I suggest you learn and try it if you want to create a CLI 😁
In the end, if you enjoyed this article, please
- Clap 10 times (seriously, it motivates me!) 👏
- Subscribe for more tutorials.
- Follow me to never miss an update.
Got Questions? Ask below! I'll reply to every comment.
Thank you for being a part of the community
Before you go:
- Be sure to clap and follow the writer ️👏️️
- Follow us: X | LinkedIn | YouTube | Newsletter | Podcast | Differ | Twitch
- Start your own free AI-powered blog on Differ 🚀
- Join our content creators community on Discord 🧑🏻💻
- For more content, visit plainenglish.io + stackademic.com