diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b4bd933 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.dart_tool/ +.packages +build/ +pubspec.lock diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..092af1f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,16 @@ +# 0.0.1-dev.4 + +- Added graphql api service. + +# 0.0.1-dev.3 + +- Added project upgrade command. +- Added output-directory path option. + +# 0.0.1-dev.2 + +- Add support for riverpod. + +# 0.0.1-dev.1 + +- Initial development release. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4fc33a2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 GeekyAnts + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 657fd73..2b8955a 100644 --- a/README.md +++ b/README.md @@ -1,92 +1,148 @@ -# flutter-starter-cli +# Flutter Starter CLI +The `Flutter Starter CLI` is a very useful tool that provides commands for the ease of setting up a Flutter project from scratch. +## Introduction -## Getting started +`Flutter Starter CLI` is written in the Dart programming language.\ +While working with the Flutter projects we have to write the same repetitive code multiple times but using this CLI, we can create a Flutter template on the fly.\ +Also, the basic structure of all the tests in Flutter is the same. So this CLI also provides a basic starter pack for the Flutter test as well. -To make it easy for you to get started with GitLab, here's a list of recommended next steps. +Out of the box, `Flutter Starter CLI` includes:- -Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! +- ✅ **State Management** + - BLoC - BLoC is a popular design/architectural pattern that is used to separate the logic from the UI. + - RiverPod - A state-management library that catches programming errors at compile time and ensures that the code is testable. +- ✅ **API-Services** + - Dio - A powerful HTTP client for Dart, which supports Interceptors, Global configuration, FormData, Request Cancellation, File downloading, Timeout etc. + - Http - A composable, Future-based library for making HTTP requests. + - Graphql - A standalone GraphQL client for Flutter, bringing all the features from a modern GraphQL client to one easy to use package. +- ✅ **Basic Setup** + - Themes - Themes are used to share colors and font styles throughout an app. + - Localization - The project provides support for multiple languages like English, Spanish and French. + - Routing - Go_Router is used to provide a convenient, URL-based API for navigating between different screens. +- ✅ **Testing** + - Unit - A unit test tests a single function, method, or class. + - Widget - A widget test tests a single widget. + - Integration - An integration test tests a complete app or a large part of an app. -## Add your files +## Dependencies -- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files -- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: +Currently, the `Flutter Starter CLI` depends on the following packages:- -``` -cd existing_repo -git remote add origin https://git.geekyants.com/ruchika/flutter-starter-cli.git -git branch -M main -git push -uf origin main -``` - -## Integrate with your tools - -- [ ] [Set up project integrations](https://git.geekyants.com/ruchika/flutter-starter-cli/-/settings/integrations) - -## Collaborate with your team +| Package | Version | Description | +| ------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------- | +| args | 2.3.1 | Parses raw command-line arguments into a set of options and values. | +| mason_logger | 0.2.2 | Simple logging library for CLI requests. | +| path | 1.8.2 | The path package provides common operations for manipulating paths: joining, splitting, normalizing, etc. | +| pub_updater | 0.2.2 | A Dart package which enables checking whether packages are up to date and supports updating them. | +| flutter_lints | 2.0.1 | This package contains a recommended set of lints for Flutter apps, packages, and plugins to encourage good coding practices. | -- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) -- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) -- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) -- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) -- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) +## Getting Started -## Test and Deploy +Activate globally via: -Use the built-in continuous integration in GitLab. - -- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) -- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) -- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) -- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) -- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) - -*** - -# Editing this README +```sh +dart pub global activate flutter_starter_cli +``` -When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template. +## Usage -## Suggestions for a good README -Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. +In the root of your application, run the following commands: -## Name -Choose a self-explaining name for your project. +```sh +# Show CLI version +$ flutter_starter_cli --version +``` -## Description -Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. +```sh +# Show usage help +$ flutter_starter_cli --help +``` -## Badges -On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. +```sh +# To create project +$ flutter_starter_cli create -## Visuals -Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. +# Then follow instructions +``` -## Installation -Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. +```sh +# Shorthand to create project +$ flutter_starter_cli create --state= --api= -g -t -## Usage -Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. +# Available API services (dio, http, graphql) +# Available State management (bloc, riverpod) +``` -## Support -Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. +```sh +# To upgrade project +$ flutter_starter_cli project upgrade +``` -## Roadmap -If you have ideas for releases in the future, it is a good idea to list them in the README. +## Complete Usage + +The complete usage of the create command with options and flags. + +```sh +➜ ~ flutter_starter_cli create +Creates a new flutter starter project. + +Usage: flutter_starter_cli create +-h, --help Print this usage information. + --desc The description for the project. + (defaults to "A New Flutter Project.") + --org The organization for the project. + (defaults to "com.example") +-p, --path The directory path for the project. +-s, --state The state management for the project. + [bloc, riverpod] +-a, --api The API service for the project. + [dio, http, graphql] +-t, --[no-]test Setup Test Cases. +-g, --[no-]git Initialize Git Repository. + +Run "flutter_starter_cli help" to see global options. +``` -## Contributing -State if you are open to contributions and what your requirements are for accepting them. +## Directory Structure -For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. +The complete structure of the newly created project directory looks like this: -You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. +``` +. +├── android +├── assets +├── integration_test +├── ios +└── lib + ├── api_sdk + ├── config + ├── l10n + ├── routes + ├── screens + ├── shared + ├── themes + ├── utils + ├── widgets + ├── app.dart + ├── common_export.dart + ├── main.dart +├── linux +├── macos +├── test +├── web +├── windows +├── .gitignore +├── analysis_options.yaml +├── l10n.yaml +├── pubspec.lock +├── pubspec.yaml +└── README.md +``` -## Authors and acknowledgment -Show your appreciation to those who have contributed to the project. +## Demo -## License -For open source projects, say how it is licensed. +A demo video to illustrate how to use the `Flutter_Starter_CLI` -## Project status -If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. +![demo](./flutter-starter-cli.gif) diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..f9b3034 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1 @@ +include: package:flutter_lints/flutter.yaml diff --git a/bin/flutter_starter_cli.dart b/bin/flutter_starter_cli.dart new file mode 100644 index 0000000..8240bc2 --- /dev/null +++ b/bin/flutter_starter_cli.dart @@ -0,0 +1,12 @@ +import 'dart:io'; + +import 'package:flutter_starter_cli/src/command_runner.dart'; + +Future main(List args) async { + await _flushThenExit(await FlutterStarterCliCommandRunner().run(args)); +} + +Future _flushThenExit(int status) { + return Future.wait([stdout.close(), stderr.close()]) + .then((_) => exit(status)); +} diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..80e0310 --- /dev/null +++ b/example/README.md @@ -0,0 +1,9 @@ +# Example + +```sh +# Activate cli +dart pub global activate flutter_starter_cli + +# See usage +flutter_starter_cli --help +``` diff --git a/flutter-starter-cli.gif b/flutter-starter-cli.gif new file mode 100644 index 0000000..f7c9008 Binary files /dev/null and b/flutter-starter-cli.gif differ diff --git a/lib/flutter_starter_cli.dart b/lib/flutter_starter_cli.dart new file mode 100644 index 0000000..ccb4aa5 --- /dev/null +++ b/lib/flutter_starter_cli.dart @@ -0,0 +1,8 @@ +/// ```sh +/// # Activate cli +/// dart pub global activate flutter_starter_cli +/// +/// # See usage +/// flutter_starter_cli --help +/// ``` +library flutter_starter_cli; diff --git a/lib/src/cli/actions.dart b/lib/src/cli/actions.dart new file mode 100644 index 0000000..db0ea40 --- /dev/null +++ b/lib/src/cli/actions.dart @@ -0,0 +1,105 @@ +import 'dart:io'; + +import 'package:flutter_starter_cli/src/cli/cli.dart'; +import 'package:flutter_starter_cli/src/utils.dart'; + +class Actions { + static Future _addStep(message) async { + Status.start('Adding $message...'); + await Future.delayed(const Duration(seconds: 1)); + Status.complete('$message Added!!!'); + } + + static Future createProject(String path, String state) async { + Status.start('Project Creating...'); + try { + await Cli.cloneProject(path, state); + Status.complete('Project Created!!!'); + } catch (_) { + Status.fail('Project Creation Failed!!!'); + } + } + + static Future generateFiles( + String path, + String name, + String desc, + String org, + String api, + bool test, + ) async { + Status.start('Running Basic Setup...'); + try { + await Cli.removeFiles(path, api, test); + Status.complete('Basic Setup Completed!!!'); + } catch (_) { + Status.fail('Setup Failed.'); + } + await Future.wait( + Directory(path) + .listSync(recursive: true) + .whereType() + .map((_) async { + var file = _; + try { + final contents = await file.readAsString(); + file = await file.writeAsString(contents + .replaceAll('flutter_starter', name) + .replaceAll('Flutter Starter', name) + .replaceAll('A new Flutter project.', desc) + .replaceAll('com.example', org) + .replaceAll( + 'api_sdk/dio_api_sdk.dart', 'api_sdk/${api}_api_sdk.dart')); + } catch (_) {} + }), + ); + await _addStep('State Management'); + await _addStep('API Service'); + await _addStep('Localization'); + await _addStep('Routes'); + await _addStep('Themes'); + if (test) await _addStep('Test Cases'); + } + + static Future setupPackages(String path, String api, bool test) async { + Status.start('Adding Dependencies...'); + try { + await Cli.removePackages(path, api, test); + Status.complete('Dependencies Added!!!'); + } catch (_) { + Status.fail('Pub Add Failed.'); + } + } + + static Future getPackages(String path) async { + Status.start('Running Pub Get...'); + try { + await Cli.getPackages(path); + Status.complete('Pub Get Completed!!!'); + } catch (_) { + Status.fail('Pub Get Failed.'); + } + } + + static Future initializeGit(String path, bool git) async { + if (git) { + Status.start('Initialize Git...'); + try { + await Cli.initializeGit(path); + Status.complete('Git Initialized!!!'); + } catch (_) { + Status.fail('Git Not Installed.'); + } + } + } + + static Future upgradeProject(String path, bool major) async { + Status.start('Upgrading Project...'); + try { + await Cli.upgradeProject(path, major); + Status.complete('Project Upgraded!!!'); + } catch (_) { + Status.fail('Project Upgrade Failed.'); + } + } +} diff --git a/lib/src/cli/cli.dart b/lib/src/cli/cli.dart new file mode 100644 index 0000000..39e9086 --- /dev/null +++ b/lib/src/cli/cli.dart @@ -0,0 +1,137 @@ +import 'dart:io'; + +import 'package:path/path.dart'; + +import 'package:flutter_starter_cli/src/utils.dart'; + +class Cli { + static Future _run( + String cmd, + List args, { + String? path, + }) async { + final result = await Process.run( + cmd, + args, + workingDirectory: path, + runInShell: true, + ); + return result; + } + + static _delete(target) { + if (target.existsSync()) target.deleteSync(recursive: true); + } + + static Future cloneProject(String path, String state) async { + await _run( + 'git', + [ + 'clone', + 'https://github.com/Geekyants/flutter-starter.git', + '--branch', + state, + '--single-branch', + path + ], + path: path, + ); + } + + static Future removeFiles(String path, String api, bool test) async { + File file; + Directory folder; + folder = Directory(join(path, '.git')); + await _delete(folder); + final String apiSdk = join(path, 'lib', 'api_sdk'); + if (api == APIService.dio.name) { + folder = Directory(join(apiSdk, 'http')); + await _delete(folder); + file = File(join(apiSdk, 'http_api_sdk.dart')); + await _delete(file); + folder = Directory(join(apiSdk, 'graphql')); + await _delete(folder); + file = File(join(apiSdk, 'graphql_api_sdk.dart')); + await _delete(file); + } else if (api == APIService.http.name) { + folder = Directory(join(apiSdk, 'dio')); + await _delete(folder); + file = File(join(apiSdk, 'dio_api_sdk.dart')); + await _delete(file); + folder = Directory(join(apiSdk, 'graphql')); + await _delete(folder); + file = File(join(apiSdk, 'graphql_api_sdk.dart')); + await _delete(file); + } else if (api == APIService.graphql.name) { + folder = Directory(join(apiSdk, 'http')); + await _delete(folder); + file = File(join(apiSdk, 'http_api_sdk.dart')); + await _delete(file); + folder = Directory(join(apiSdk, 'dio')); + await _delete(folder); + file = File(join(apiSdk, 'dio_api_sdk.dart')); + await _delete(file); + } + if (!test) { + folder = Directory(join(path, 'integration_test')); + await _delete(folder); + folder = Directory(join(path, 'test')); + await _delete(folder); + } + } + + static Future removePackages(String path, String api, bool test) async { + var args = []; + if (api == APIService.dio.name) { + args + ..add('http') + ..add('http_interceptor') + ..add('graphql_flutter'); + } else if (api == APIService.http.name) { + args + ..add('dio') + ..add('retrofit') + ..add('graphql_flutter'); + } else if (api == APIService.graphql.name) { + args + ..add('http') + ..add('http_interceptor') + ..add('dio') + ..add('retrofit'); + } + if (!test) { + args + ..add('bloc_test') + ..add('mocktail'); + } + await _run( + 'flutter', + ['pub', 'remove', ...args], + path: path, + ); + } + + static Future getPackages(String path) async { + await _run( + 'flutter', + ['pub', 'get'], + path: path, + ); + } + + static Future initializeGit(String path) async { + await _run( + 'git', + ['init'], + path: path, + ); + } + + static Future upgradeProject(String path, bool major) async { + await _run( + 'flutter', + ['pub', 'upgrade', major ? '--major-versions' : ''], + path: path, + ); + } +} diff --git a/lib/src/command_runner.dart b/lib/src/command_runner.dart new file mode 100644 index 0000000..472d2c7 --- /dev/null +++ b/lib/src/command_runner.dart @@ -0,0 +1,115 @@ +import 'package:args/args.dart'; +import 'package:args/command_runner.dart'; +import 'package:mason_logger/mason_logger.dart'; +import 'package:pub_updater/pub_updater.dart'; + +import 'package:flutter_starter_cli/src/commands/commands.dart'; +import 'package:flutter_starter_cli/src/commands/subcommands/subcommands.dart'; +import 'package:flutter_starter_cli/src/utils.dart'; +import 'package:flutter_starter_cli/src/version.dart'; + +const executableName = 'flutter_starter_cli'; +const packageName = 'flutter_starter_cli'; +const description = 'Flutter Starter CLI'; + +class FlutterStarterCliCommandRunner extends CommandRunner { + FlutterStarterCliCommandRunner({ + Logger? logger, + PubUpdater? pubUpdater, + }) : _logger = logger ?? Logger(), + _pubUpdater = pubUpdater ?? PubUpdater(), + super(executableName, description) { + argParser + ..addFlag( + 'version', + abbr: 'v', + negatable: false, + help: 'Print the current version.', + ) + ..addFlag( + 'verbose', + help: 'Noisy logging, including all shell commands executed.', + ); + + addCommand(CreateCommand(logger: _logger)); + addCommand(UpdateCommand(logger: _logger, pubUpdater: _pubUpdater)); + addCommand(ProjectCommand()..addSubcommand(ProjectUpgradeSubcommand())); + } + + final Logger _logger; + final PubUpdater _pubUpdater; + + @override + Future run(Iterable args) async { + Status.init(_logger); + try { + final topLevelResults = parse(args); + if (topLevelResults['verbose'] == true) { + _logger.level = Level.verbose; + } + return await runCommand(topLevelResults) ?? ExitCode.success.code; + } on FormatException catch (e, stackTrace) { + _logger + ..err(e.message) + ..err('$stackTrace') + ..info('') + ..info(usage); + return ExitCode.usage.code; + } on UsageException catch (e) { + _logger + ..err(e.message) + ..info('') + ..info(e.usage); + return ExitCode.usage.code; + } + } + + @override + Future runCommand(ArgResults topLevelResults) async { + _logger + ..detail('Argument information:') + ..detail(' Top level options:'); + for (final option in topLevelResults.options) { + if (topLevelResults.wasParsed(option)) { + _logger.detail(' - $option: ${topLevelResults[option]}'); + } + } + if (topLevelResults.command != null) { + final commandResult = topLevelResults.command!; + _logger + ..detail(' Command: ${commandResult.name}') + ..detail(' Command options:'); + for (final option in commandResult.options) { + if (commandResult.wasParsed(option)) { + _logger.detail(' - $option: ${commandResult[option]}'); + } + } + } + + final int? exitCode; + if (topLevelResults['version'] == true) { + _logger.info(packageVersion); + exitCode = ExitCode.success.code; + } else { + exitCode = await super.runCommand(topLevelResults); + } + await _checkForUpdates(); + return exitCode; + } + + Future _checkForUpdates() async { + try { + final latestVersion = await _pubUpdater.getLatestVersion(packageName); + final isUpToDate = packageVersion == latestVersion; + if (!isUpToDate) { + _logger + ..info('') + ..info( + ''' +${lightYellow.wrap('Update available!')} ${lightCyan.wrap(packageVersion)} \u2192 ${lightCyan.wrap(latestVersion)} +Run ${lightCyan.wrap('flutter_starter_cli update')} to update''', + ); + } + } catch (_) {} + } +} diff --git a/lib/src/commands/commands.dart b/lib/src/commands/commands.dart new file mode 100644 index 0000000..ebaeeba --- /dev/null +++ b/lib/src/commands/commands.dart @@ -0,0 +1,3 @@ +export 'create_command.dart'; +export 'project_command.dart'; +export 'update_command.dart'; diff --git a/lib/src/commands/create_command.dart b/lib/src/commands/create_command.dart new file mode 100644 index 0000000..df4bb2d --- /dev/null +++ b/lib/src/commands/create_command.dart @@ -0,0 +1,181 @@ +import 'dart:io'; + +import 'package:args/command_runner.dart'; +import 'package:mason_logger/mason_logger.dart'; +import 'package:path/path.dart'; + +import 'package:flutter_starter_cli/src/cli/actions.dart'; +import 'package:flutter_starter_cli/src/command_runner.dart'; +import 'package:flutter_starter_cli/src/utils.dart'; + +class CreateCommand extends Command { + CreateCommand({ + required Logger logger, + }) : _logger = logger { + argParser + ..addOption( + 'desc', + help: 'The description for the project.', + defaultsTo: 'A New Flutter Project.', + ) + ..addOption( + 'org', + help: 'The organization for the project.', + defaultsTo: 'com.example', + ) + ..addOption( + 'path', + abbr: 'p', + help: 'The directory path for the project.', + ) + ..addOption( + 'state', + abbr: 's', + help: 'The state management for the project.', + allowed: [StateManagement.bloc.name, StateManagement.riverpod.name], + ) + ..addOption( + 'api', + abbr: 'a', + help: 'The API service for the project.', + allowed: [ + APIService.dio.name, + APIService.http.name, + APIService.graphql.name + ], + ) + ..addFlag( + 'test', + abbr: 't', + help: 'Setup Test Cases.', + defaultsTo: null, + ) + ..addFlag( + 'git', + abbr: 'g', + help: 'Initialize Git Repository.', + defaultsTo: null, + ); + } + + final Logger _logger; + + @override + String get description => 'Creates a new flutter starter project.'; + + @override + String get name => 'create'; + + @override + String get invocation => '$executableName $name '; + + @override + Future run() async { + final name = _name; + final desc = _desc; + final org = _org; + final state = _state; + final api = _api; + final test = state == StateManagement.bloc.name ? _test : false; + final git = _git; + final dir = _dir(name); + final path = join(Directory.current.path, dir); + final target = Directory(path); + if (!target.existsSync()) target.createSync(recursive: true); + await onGenerateComplete( + _logger, path, name, desc, org, state, api, test, git); + _logger.success('''Your Project is Ready to Use 🚀 +Type:- + \$ cd $dir + \$ flutter run +In order to run your application.'''); + return ExitCode.success.code; + } + + String get _name { + final args = argResults!.rest; + if (args.isEmpty) { + return _logger.prompt( + 'Name of the Project?', + defaultValue: 'flutter_starter', + ); + } + return args.first; + } + + String get _state { + return argResults?['state'] ?? + _logger.chooseOne( + 'Select the State Management', + choices: [StateManagement.bloc.name, StateManagement.riverpod.name], + defaultValue: StateManagement.bloc.name, + ); + } + + String get _api { + return argResults?['api'] ?? + _logger.chooseOne( + 'Select the API Service', + choices: [ + APIService.dio.name, + APIService.http.name, + APIService.graphql.name + ], + defaultValue: APIService.dio.name, + ); + } + + String get _desc { + return argResults?['desc'] ?? + _logger.prompt( + 'Please Enter the Description', + defaultValue: 'A New Flutter Project.', + ); + } + + String get _org { + return argResults?['org'] ?? + _logger.prompt( + 'Please Enter the Organization', + defaultValue: 'com.example', + ); + } + + bool get _test { + return argResults?['test'] ?? + _logger.confirm( + 'Whether Test Cases Required?', + defaultValue: true, + ); + } + + bool get _git { + return argResults?['git'] ?? + _logger.confirm( + 'Initialize Git Repository?', + defaultValue: false, + ); + } + + String _dir(name) { + return argResults?['path'] ?? name; + } + + Future onGenerateComplete( + Logger logger, + String path, + String name, + String desc, + String org, + String state, + String api, + bool test, + bool git, + ) async { + await Actions.createProject(path, state); + await Actions.generateFiles(path, name, desc, org, api, test); + await Actions.setupPackages(path, api, test); + await Actions.getPackages(path); + await Actions.initializeGit(path, git); + } +} diff --git a/lib/src/commands/project_command.dart b/lib/src/commands/project_command.dart new file mode 100644 index 0000000..0da82aa --- /dev/null +++ b/lib/src/commands/project_command.dart @@ -0,0 +1,11 @@ +import 'package:args/command_runner.dart'; + +class ProjectCommand extends Command { + ProjectCommand(); + + @override + String get description => 'Commands related to the Project.'; + + @override + String get name => 'project'; +} diff --git a/lib/src/commands/subcommands/subcommands.dart b/lib/src/commands/subcommands/subcommands.dart new file mode 100644 index 0000000..020fc9a --- /dev/null +++ b/lib/src/commands/subcommands/subcommands.dart @@ -0,0 +1 @@ +export 'upgrade_subcommand.dart'; diff --git a/lib/src/commands/subcommands/upgrade_subcommand.dart b/lib/src/commands/subcommands/upgrade_subcommand.dart new file mode 100644 index 0000000..01e77a4 --- /dev/null +++ b/lib/src/commands/subcommands/upgrade_subcommand.dart @@ -0,0 +1,35 @@ +import 'dart:io'; + +import 'package:args/command_runner.dart'; +import 'package:mason_logger/mason_logger.dart'; + +import 'package:flutter_starter_cli/src/cli/actions.dart'; + +class ProjectUpgradeSubcommand extends Command { + ProjectUpgradeSubcommand() { + argParser.addFlag( + 'major', + abbr: 'm', + help: 'Upgrade major versions.', + defaultsTo: false, + ); + } + + @override + String get description => 'Upgrade the project dependencies.'; + + @override + String get name => 'upgrade'; + + @override + Future run() async { + final path = Directory.current.path; + final major = _major; + await Actions.upgradeProject(path, major); + return ExitCode.success.code; + } + + bool get _major { + return argResults?['major']; + } +} diff --git a/lib/src/commands/update_command.dart b/lib/src/commands/update_command.dart new file mode 100644 index 0000000..1b9a46d --- /dev/null +++ b/lib/src/commands/update_command.dart @@ -0,0 +1,55 @@ +import 'package:args/command_runner.dart'; +import 'package:mason_logger/mason_logger.dart'; +import 'package:pub_updater/pub_updater.dart'; + +import 'package:flutter_starter_cli/src/command_runner.dart'; +import 'package:flutter_starter_cli/src/version.dart'; + +class UpdateCommand extends Command { + UpdateCommand({ + required Logger logger, + PubUpdater? pubUpdater, + }) : _logger = logger, + _pubUpdater = pubUpdater ?? PubUpdater(); + + final Logger _logger; + final PubUpdater _pubUpdater; + + @override + String get description => 'Update the CLI.'; + + @override + String get name => 'update'; + + @override + Future run() async { + final updateCheckProgress = _logger.progress('Checking for updates'); + late final String latestVersion; + try { + latestVersion = await _pubUpdater.getLatestVersion(packageName); + } catch (error) { + updateCheckProgress.fail(); + _logger.err('$error'); + return ExitCode.software.code; + } + updateCheckProgress.complete('Checked for updates'); + + final isUpToDate = packageVersion == latestVersion; + if (isUpToDate) { + _logger.info('CLI is already at the latest version.'); + return ExitCode.success.code; + } + + final updateProgress = _logger.progress('Updating to $latestVersion'); + try { + await _pubUpdater.update(packageName: packageName); + } catch (error) { + updateProgress.fail(); + _logger.err('$error'); + return ExitCode.software.code; + } + updateProgress.complete('Updated to $latestVersion'); + + return ExitCode.success.code; + } +} diff --git a/lib/src/utils.dart b/lib/src/utils.dart new file mode 100644 index 0000000..d37ad7a --- /dev/null +++ b/lib/src/utils.dart @@ -0,0 +1,25 @@ +import 'package:mason_logger/mason_logger.dart'; + +enum APIService { + dio, + http, + graphql, +} + +enum StateManagement { + bloc, + riverpod, +} + +class Status { + static late Progress progress; + static late Logger logger; + + static init(log) => logger = log; + + static start(String message) => progress = logger.progress(message); + + static complete(String message) => progress.complete(message); + + static fail(String message) => progress.fail(message); +} diff --git a/lib/src/version.dart b/lib/src/version.dart new file mode 100644 index 0000000..f7e4563 --- /dev/null +++ b/lib/src/version.dart @@ -0,0 +1 @@ +const packageVersion = '0.0.1-dev.4'; diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..4de4149 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,21 @@ +name: flutter_starter_cli +description: A command line tool that provides ease of setting up a Flutter project. +version: 0.0.1-dev.4 + +homepage: https://flutter-starter.geekyants.com +repository: https://github.com/GeekyAnts/flutter-starter-cli + +environment: + sdk: ">=3.0.0 <4.0.0" + +dependencies: + args: ^2.4.2 + mason_logger: ^0.2.6 + path: ^1.8.3 + pub_updater: ^0.3.1 + +dev_dependencies: + flutter_lints: ^2.0.1 + +executables: + flutter_starter_cli: