From 01eda4e17aed63c4b93e13eeec27e4f5a4f546d5 Mon Sep 17 00:00:00 2001 From: AlexitoSnow Date: Sat, 4 Jul 2026 21:32:28 -0500 Subject: [PATCH 1/5] ci(github): configure release-please and update actions This commit removes unused github actions like cspell and dependabot, and introduces release-please for automated versioning and changelog generation. It also updates the main workflow to run tests and checks. --- .github/cspell.json | 20 ------------ .github/dependabot.yaml | 11 ------- .github/workflows/license_check.yaml | 25 --------------- .github/workflows/release_please.yaml | 35 +++++++++++++++++++++ .github/workflows/xdevs_cli.yaml | 45 +++++++++------------------ 5 files changed, 49 insertions(+), 87 deletions(-) delete mode 100644 .github/cspell.json delete mode 100644 .github/dependabot.yaml delete mode 100644 .github/workflows/license_check.yaml create mode 100644 .github/workflows/release_please.yaml diff --git a/.github/cspell.json b/.github/cspell.json deleted file mode 100644 index 1b78898..0000000 --- a/.github/cspell.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": "0.2", - "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", - "dictionaries": ["vgv_allowed", "vgv_forbidden"], - "dictionaryDefinitions": [ - { - "name": "vgv_allowed", - "path": "https://raw.githubusercontent.com/verygoodopensource/very_good_dictionaries/main/allowed.txt", - "description": "Allowed VGV Spellings" - }, - { - "name": "vgv_forbidden", - "path": "https://raw.githubusercontent.com/verygoodopensource/very_good_dictionaries/main/forbidden.txt", - "description": "Forbidden VGV Spellings" - } - ], - "useGitignore": true, - "words": [ - ] -} diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml deleted file mode 100644 index 63b035c..0000000 --- a/.github/dependabot.yaml +++ /dev/null @@ -1,11 +0,0 @@ -version: 2 -enable-beta-ecosystems: true -updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" - - package-ecosystem: "pub" - directory: "/" - schedule: - interval: "daily" diff --git a/.github/workflows/license_check.yaml b/.github/workflows/license_check.yaml deleted file mode 100644 index ce6acfc..0000000 --- a/.github/workflows/license_check.yaml +++ /dev/null @@ -1,25 +0,0 @@ -name: license_check - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -on: - pull_request: - branches: - - main - paths: - - "pubspec.yaml" - - ".github/workflows/license_check.yaml" - push: - branches: - - main - paths: - - "pubspec.yaml" - - ".github/workflows/license_check.yaml" - -jobs: - license_check: - uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/license_check.yml@v1 - with: - allowed: "MIT,BSD-3-Clause,BSD-2-Clause,Apache-2.0" diff --git a/.github/workflows/release_please.yaml b/.github/workflows/release_please.yaml new file mode 100644 index 0000000..e5a9f62 --- /dev/null +++ b/.github/workflows/release_please.yaml @@ -0,0 +1,35 @@ +name: Release Please + +on: + push: + branches: + - main + +permissions: + contents: write + pull-requests: write + +jobs: + release-please: + runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release.outputs.release_created }} + steps: + - id: release + uses: googleapis/release-please-action@v4 + with: + release-type: dart + + publish: + needs: release-please + if: ${{ needs.release-please.outputs.release_created }} + runs-on: ubuntu-latest + permissions: + id-token: write # Required for pub.dev OIDC authentication + steps: + - uses: actions/checkout@v4 + - uses: dart-lang/setup-dart@v1 + - name: Install dependencies + run: dart pub get + - name: Publish to pub.dev + run: dart pub publish --force diff --git a/.github/workflows/xdevs_cli.yaml b/.github/workflows/xdevs_cli.yaml index 808dea9..18b6798 100644 --- a/.github/workflows/xdevs_cli.yaml +++ b/.github/workflows/xdevs_cli.yaml @@ -1,8 +1,4 @@ -name: xdevs_cli - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true +name: xdevs_cli CI on: pull_request: @@ -21,37 +17,24 @@ on: - "pubspec.yaml" jobs: - semantic-pull-request: - uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/semantic_pull_request.yml@v1 - build: - uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/dart_package.yml@v1 - with: - dart_sdk: "3.11.0" - - spell-check: - uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/spell_check.yml@v1 - with: - includes: | - **/*.md - !brick/**/*.md - .*/**/*.md - modified_files_only: false - - verify-version: runs-on: ubuntu-latest steps: - - name: ๐Ÿ“š Git Checkout - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - - name: ๐ŸŽฏ Setup Dart - uses: dart-lang/setup-dart@v1.7.2 + - name: Setup Dart + uses: dart-lang/setup-dart@v1 with: sdk: "3.11.0" - - name: ๐Ÿ“ฆ Install Dependencies - run: | - dart pub get + - name: Install Dependencies + run: dart pub get + + - name: Verify Formatting + run: dart format --output=none --set-exit-if-changed . + + - name: Analyze Project + run: dart analyze --fatal-infos - - name: ๐Ÿ”Ž Verify version - run: dart run test --run-skipped -t version-verify + - name: Run Tests + run: dart test From 83ab27b45ddf945eb5055e48c6121a3c288ee4b5 Mon Sep 17 00:00:00 2001 From: AlexitoSnow Date: Sat, 4 Jul 2026 21:32:33 -0500 Subject: [PATCH 2/5] refactor(bricks): reorganize bricks directory and introduce bundles This commit moves the bricks folder to the root of the project and introduces Dart bundles inside lib/src/bundles. This allows the CLI to compile bricks directly into the executable, avoiding runtime path issues. --- bin/bricks/base_project/CHANGELOG.md | 5 - bin/bricks/base_project/LICENSE | 3 - bin/bricks/base_project/README.md | 3 - bin/bricks/base_project/__brick__/README.md | 48 ----- .../lib/features/features_screens.dart | 4 - .../lib/features/sample/data/data.dart | 10 - .../lib/features/sample/domain/domain.dart | 9 - .../presentation/providers/providers.dart | 5 - .../presentation/viewmodels/sample_state.dart | 12 -- .../presentation/viewmodels/viewmodels.dart | 5 - bin/bricks/feature_module/CHANGELOG.md | 3 - bin/bricks/feature_module/LICENSE | 1 - bin/bricks/feature_module/README.md | 27 --- .../data/data.dart | 7 - .../domain/domain.dart | 7 - .../presentation/providers/providers.dart | 4 - .../presentation/viewmodels/viewmodels.dart | 4 - bin/bricks/mason.yaml | 17 -- .../base_project/__brick__/.gitignore | 0 bricks/base_project/__brick__/README.md | 45 ++++ .../base_project/__brick__/assets/.gitkeep | 0 .../__brick__/lib/core/constants/strings.dart | 0 .../__brick__/lib/core/di/.gitkeep | 0 .../__brick__/lib/core/errors/.gitkeep | 0 .../__brick__/lib/core/network/.gitkeep | 0 .../__brick__/lib/core/storage/.gitkeep | 0 .../__brick__/lib/core/themes/app_theme.dart | 0 .../__brick__/lib/core/utils/.gitkeep | 0 .../__brick__/lib/features/app/app.dart | 0 .../lib/features/app/app_router.dart | 0 .../lib/features/sample/data/data.dart | 7 + .../data/datasources/sample_datasource.dart | 0 .../sample/data/models/online_data.dart | 0 .../online_sample_data_repository.dart | 0 .../lib/features/sample/domain/domain.dart | 8 + .../sample/domain/entities/sample_data.dart | 0 .../repositories/sample_data_repository.dart | 0 .../features/sample/domain/usecases/.gitkeep | 0 .../usecases/get_sample_data_usecase.dart | 8 + .../presentation/providers/providers.dart | 1 + .../providers/sample_provider.dart | 9 +- .../viewmodels/sample_viewmodel.dart | 12 ++ .../presentation/viewmodels/viewmodels.dart | 1 + .../presentation/views/sample_screen.dart | 6 +- .../sample/presentation/widgets/widgets.dart | 0 .../lib/features/shared/helpers/.gitkeep | 0 .../__brick__/lib/features/shared/shared.dart | 0 .../lib/features/shared/widgets/.gitkeep | 0 .../base_project/__brick__/lib/main.dart | 0 .../base_project/__brick__/pubspec.yaml | 0 .../bricks => bricks}/base_project/brick.yaml | 0 .../data/data.dart | 7 + .../data/datasources/.gitkeep | 0 .../data/models/.gitkeep | 0 .../data/repositories_impl/.gitkeep | 0 .../domain/domain.dart | 8 + .../domain/entities/.gitkeep | 0 .../domain/repositories/.gitkeep | 0 ...ature_name.snakeCase()}}_data_usecase.dart | 8 + .../presentation/providers/providers.dart | 1 + ...{{feature_name.snakeCase()}}_provider.dart | 14 ++ .../presentation/viewmodels/viewmodels.dart | 1 + ...{feature_name.snakeCase()}}_viewmodel.dart | 12 ++ .../{{feature_name.snakeCase()}}_screen.dart | 10 +- .../presentation/widgets/widgets.dart | 0 .../feature_module/brick.yaml | 0 bricks/mason.yaml | 5 + lib/src/bundles/base_project_bundle.dart | 200 ++++++++++++++++++ lib/src/bundles/feature_module_bundle.dart | 135 ++++++++++++ 69 files changed, 492 insertions(+), 180 deletions(-) delete mode 100644 bin/bricks/base_project/CHANGELOG.md delete mode 100644 bin/bricks/base_project/LICENSE delete mode 100644 bin/bricks/base_project/README.md delete mode 100644 bin/bricks/base_project/__brick__/README.md delete mode 100644 bin/bricks/base_project/__brick__/lib/features/features_screens.dart delete mode 100644 bin/bricks/base_project/__brick__/lib/features/sample/data/data.dart delete mode 100644 bin/bricks/base_project/__brick__/lib/features/sample/domain/domain.dart delete mode 100644 bin/bricks/base_project/__brick__/lib/features/sample/presentation/providers/providers.dart delete mode 100644 bin/bricks/base_project/__brick__/lib/features/sample/presentation/viewmodels/sample_state.dart delete mode 100644 bin/bricks/base_project/__brick__/lib/features/sample/presentation/viewmodels/viewmodels.dart delete mode 100644 bin/bricks/feature_module/CHANGELOG.md delete mode 100644 bin/bricks/feature_module/LICENSE delete mode 100644 bin/bricks/feature_module/README.md delete mode 100644 bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/data.dart delete mode 100644 bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/domain/domain.dart delete mode 100644 bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/providers/providers.dart delete mode 100644 bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/viewmodels/viewmodels.dart delete mode 100644 bin/bricks/mason.yaml rename {bin/bricks => bricks}/base_project/__brick__/.gitignore (100%) create mode 100644 bricks/base_project/__brick__/README.md rename {bin/bricks => bricks}/base_project/__brick__/assets/.gitkeep (100%) rename {bin/bricks => bricks}/base_project/__brick__/lib/core/constants/strings.dart (100%) rename {bin/bricks => bricks}/base_project/__brick__/lib/core/di/.gitkeep (100%) rename {bin/bricks => bricks}/base_project/__brick__/lib/core/errors/.gitkeep (100%) rename {bin/bricks => bricks}/base_project/__brick__/lib/core/network/.gitkeep (100%) rename {bin/bricks => bricks}/base_project/__brick__/lib/core/storage/.gitkeep (100%) rename {bin/bricks => bricks}/base_project/__brick__/lib/core/themes/app_theme.dart (100%) rename {bin/bricks => bricks}/base_project/__brick__/lib/core/utils/.gitkeep (100%) rename {bin/bricks => bricks}/base_project/__brick__/lib/features/app/app.dart (100%) rename {bin/bricks => bricks}/base_project/__brick__/lib/features/app/app_router.dart (100%) create mode 100644 bricks/base_project/__brick__/lib/features/sample/data/data.dart rename {bin/bricks => bricks}/base_project/__brick__/lib/features/sample/data/datasources/sample_datasource.dart (100%) rename {bin/bricks => bricks}/base_project/__brick__/lib/features/sample/data/models/online_data.dart (100%) rename {bin/bricks => bricks}/base_project/__brick__/lib/features/sample/data/repositories_impl/online_sample_data_repository.dart (100%) create mode 100644 bricks/base_project/__brick__/lib/features/sample/domain/domain.dart rename {bin/bricks => bricks}/base_project/__brick__/lib/features/sample/domain/entities/sample_data.dart (100%) rename {bin/bricks => bricks}/base_project/__brick__/lib/features/sample/domain/repositories/sample_data_repository.dart (100%) rename {bin/bricks => bricks}/base_project/__brick__/lib/features/sample/domain/usecases/.gitkeep (100%) create mode 100644 bricks/base_project/__brick__/lib/features/sample/domain/usecases/get_sample_data_usecase.dart create mode 100644 bricks/base_project/__brick__/lib/features/sample/presentation/providers/providers.dart rename {bin/bricks => bricks}/base_project/__brick__/lib/features/sample/presentation/providers/sample_provider.dart (69%) create mode 100644 bricks/base_project/__brick__/lib/features/sample/presentation/viewmodels/sample_viewmodel.dart create mode 100644 bricks/base_project/__brick__/lib/features/sample/presentation/viewmodels/viewmodels.dart rename {bin/bricks => bricks}/base_project/__brick__/lib/features/sample/presentation/views/sample_screen.dart (78%) rename {bin/bricks => bricks}/base_project/__brick__/lib/features/sample/presentation/widgets/widgets.dart (100%) rename {bin/bricks => bricks}/base_project/__brick__/lib/features/shared/helpers/.gitkeep (100%) rename {bin/bricks => bricks}/base_project/__brick__/lib/features/shared/shared.dart (100%) rename {bin/bricks => bricks}/base_project/__brick__/lib/features/shared/widgets/.gitkeep (100%) rename {bin/bricks => bricks}/base_project/__brick__/lib/main.dart (100%) rename {bin/bricks => bricks}/base_project/__brick__/pubspec.yaml (100%) rename {bin/bricks => bricks}/base_project/brick.yaml (100%) create mode 100644 bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/data.dart rename {bin/bricks => bricks}/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/datasources/.gitkeep (100%) rename {bin/bricks => bricks}/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/models/.gitkeep (100%) rename {bin/bricks => bricks}/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/repositories_impl/.gitkeep (100%) create mode 100644 bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/domain/domain.dart rename {bin/bricks => bricks}/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/domain/entities/.gitkeep (100%) rename {bin/bricks => bricks}/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/domain/repositories/.gitkeep (100%) create mode 100644 bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/domain/usecases/get_{{feature_name.snakeCase()}}_data_usecase.dart create mode 100644 bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/providers/providers.dart create mode 100644 bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/providers/{{feature_name.snakeCase()}}_provider.dart create mode 100644 bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/viewmodels/viewmodels.dart create mode 100644 bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/viewmodels/{{feature_name.snakeCase()}}_viewmodel.dart rename {bin/bricks => bricks}/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/views/{{feature_name.snakeCase()}}_screen.dart (55%) rename {bin/bricks => bricks}/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/widgets/widgets.dart (100%) rename {bin/bricks => bricks}/feature_module/brick.yaml (100%) create mode 100644 bricks/mason.yaml create mode 100644 lib/src/bundles/base_project_bundle.dart create mode 100644 lib/src/bundles/feature_module_bundle.dart diff --git a/bin/bricks/base_project/CHANGELOG.md b/bin/bricks/base_project/CHANGELOG.md deleted file mode 100644 index b1c1ab2..0000000 --- a/bin/bricks/base_project/CHANGELOG.md +++ /dev/null @@ -1,5 +0,0 @@ -# Changelog - -## 0.1.0+1 - -- Initial version. \ No newline at end of file diff --git a/bin/bricks/base_project/LICENSE b/bin/bricks/base_project/LICENSE deleted file mode 100644 index a7ff3a5..0000000 --- a/bin/bricks/base_project/LICENSE +++ /dev/null @@ -1,3 +0,0 @@ -MIT License - -Copyright (c) 2026 \ No newline at end of file diff --git a/bin/bricks/base_project/README.md b/bin/bricks/base_project/README.md deleted file mode 100644 index df81596..0000000 --- a/bin/bricks/base_project/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# base_project - -Generates the base Flutter project structure. \ No newline at end of file diff --git a/bin/bricks/base_project/__brick__/README.md b/bin/bricks/base_project/__brick__/README.md deleted file mode 100644 index ff6f883..0000000 --- a/bin/bricks/base_project/__brick__/README.md +++ /dev/null @@ -1,48 +0,0 @@ -## {{project_name.snakeCase()}} - -## Starter Dependencies - -- [flutter_riverpod](https://pub.dev/packages/flutter_riverpod) -- [freezed_annotation](https://pub.dev/packages/freezed_annotation) -- [go_router](https://pub.dev/packages/go_router) -- [json_annotation](https://pub.dev/packages/json_annotation) -- [riverpod_annotation](https://pub.dev/packages/riverpod_annotation) -- [build_runner](https://pub.dev/packages/build_runner) -- [flutter_lints](https://pub.dev/packages/flutter_lints) -- [freezed](https://pub.dev/packages/freezed) -- [json_serializable](https://pub.dev/packages/json_serializable) -- [riverpod_generator](https://pub.dev/packages/riverpod_generator) - - -## Next steps - -After creating a project with `xdevs_cli create `, run the following commands: - -```sh -# Enter the project -cd - -# Run the app -flutter run - -# Create a new feature inside the project (from the project root or by passing --directory): -# Usage: xdevs_cli create feature -# Example: - -# Create a single feature -xdevs_cli create feature my_new_feature - -# Create multiple features (comma-separated). Existing features will be skipped and reported: -xdevs_cli create feature feature_one,feature_two,feature_three - -# Open the project in your editor (optional) -# code . -``` - -If you prefer the short alias for the feature subcommand, you can also use: - -```sh -xdevs_cli create f my_new_feature -``` - -Happy coding! ๐Ÿš€ \ No newline at end of file diff --git a/bin/bricks/base_project/__brick__/lib/features/features_screens.dart b/bin/bricks/base_project/__brick__/lib/features/features_screens.dart deleted file mode 100644 index 737fe87..0000000 --- a/bin/bricks/base_project/__brick__/lib/features/features_screens.dart +++ /dev/null @@ -1,4 +0,0 @@ -// Export main screens from the features to be used in the app_router - -// Sample feature -export 'sample/presentation/views/sample_screen.dart'; diff --git a/bin/bricks/base_project/__brick__/lib/features/sample/data/data.dart b/bin/bricks/base_project/__brick__/lib/features/sample/data/data.dart deleted file mode 100644 index 80191a6..0000000 --- a/bin/bricks/base_project/__brick__/lib/features/sample/data/data.dart +++ /dev/null @@ -1,10 +0,0 @@ -// Export data layer components for the sample feature - -// Datasources -export 'datasources/sample_datasource.dart'; - -// Models -export 'models/online_data.dart'; - -// Repositories (implementation) -export 'repositories_impl/online_sample_data_repository.dart'; diff --git a/bin/bricks/base_project/__brick__/lib/features/sample/domain/domain.dart b/bin/bricks/base_project/__brick__/lib/features/sample/domain/domain.dart deleted file mode 100644 index 48f250b..0000000 --- a/bin/bricks/base_project/__brick__/lib/features/sample/domain/domain.dart +++ /dev/null @@ -1,9 +0,0 @@ -// Export domain layer components for the sample feature - -// Entities -export 'entities/sample_data.dart'; - -// Repositories (abstract) -export 'repositories/sample_data_repository.dart'; - -// Use cases diff --git a/bin/bricks/base_project/__brick__/lib/features/sample/presentation/providers/providers.dart b/bin/bricks/base_project/__brick__/lib/features/sample/presentation/providers/providers.dart deleted file mode 100644 index f8876fd..0000000 --- a/bin/bricks/base_project/__brick__/lib/features/sample/presentation/providers/providers.dart +++ /dev/null @@ -1,5 +0,0 @@ -// Export providers to be used in the feature -// It's not common to export providers outside of the presentation layer -// This file is useful for views that need to access multiple providers without -// importing them individually -export 'sample_provider.dart'; diff --git a/bin/bricks/base_project/__brick__/lib/features/sample/presentation/viewmodels/sample_state.dart b/bin/bricks/base_project/__brick__/lib/features/sample/presentation/viewmodels/sample_state.dart deleted file mode 100644 index 94a662d..0000000 --- a/bin/bricks/base_project/__brick__/lib/features/sample/presentation/viewmodels/sample_state.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'sample_state.freezed.dart'; -part 'sample_state.g.dart'; - -@freezed -abstract class SampleState with _$SampleState { - factory SampleState({required String sampleData}) = _SampleState; - - factory SampleState.fromJson(Map json) => - _$SampleStateFromJson(json); -} diff --git a/bin/bricks/base_project/__brick__/lib/features/sample/presentation/viewmodels/viewmodels.dart b/bin/bricks/base_project/__brick__/lib/features/sample/presentation/viewmodels/viewmodels.dart deleted file mode 100644 index 5433478..0000000 --- a/bin/bricks/base_project/__brick__/lib/features/sample/presentation/viewmodels/viewmodels.dart +++ /dev/null @@ -1,5 +0,0 @@ -// Export states to be used in the feature -// It's not common to export states outside of the presentation layer -// This file is useful for views that need to access multiple states without -// importing them individually -export 'sample_state.dart'; diff --git a/bin/bricks/feature_module/CHANGELOG.md b/bin/bricks/feature_module/CHANGELOG.md deleted file mode 100644 index f0640d6..0000000 --- a/bin/bricks/feature_module/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -# 0.1.0+1 - -- TODO: Describe initial release. diff --git a/bin/bricks/feature_module/LICENSE b/bin/bricks/feature_module/LICENSE deleted file mode 100644 index ba75c69..0000000 --- a/bin/bricks/feature_module/LICENSE +++ /dev/null @@ -1 +0,0 @@ -TODO: Add your license here. diff --git a/bin/bricks/feature_module/README.md b/bin/bricks/feature_module/README.md deleted file mode 100644 index e7081d3..0000000 --- a/bin/bricks/feature_module/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# feature_module - -[![Powered by Mason](https://img.shields.io/endpoint?url=https%3A%2F%2Ftinyurl.com%2Fmason-badge)](https://github.com/felangel/mason) - -A new brick created with the Mason CLI. - -_Generated by [mason][1] ๐Ÿงฑ_ - -## Getting Started ๐Ÿš€ - -This is a starting point for a new brick. -A few resources to get you started if this is your first brick template: - -- [Official Mason Documentation][2] -- [Code generation with Mason Blog][3] -- [Very Good Livestream: Felix Angelov Demos Mason][4] -- [Flutter Package of the Week: Mason][5] -- [Observable Flutter: Building a Mason brick][6] -- [Meet Mason: Flutter Vikings 2022][7] - -[1]: https://github.com/felangel/mason -[2]: https://docs.brickhub.dev -[3]: https://verygood.ventures/blog/code-generation-with-mason -[4]: https://youtu.be/G4PTjA6tpTU -[5]: https://youtu.be/qjA0JFiPMnQ -[6]: https://youtu.be/o8B1EfcUisw -[7]: https://youtu.be/LXhgiF5HiQg diff --git a/bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/data.dart b/bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/data.dart deleted file mode 100644 index 3fc9f89..0000000 --- a/bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/data.dart +++ /dev/null @@ -1,7 +0,0 @@ -// Export data layer components for the feature. - -// Datasources - -// Models - -// Repositories (implementation) diff --git a/bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/domain/domain.dart b/bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/domain/domain.dart deleted file mode 100644 index 0aec5c8..0000000 --- a/bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/domain/domain.dart +++ /dev/null @@ -1,7 +0,0 @@ -// Export domain layer components for the feature. - -// Entities - -// Repositories (abstract) - -// Use cases diff --git a/bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/providers/providers.dart b/bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/providers/providers.dart deleted file mode 100644 index ef86023..0000000 --- a/bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/providers/providers.dart +++ /dev/null @@ -1,4 +0,0 @@ -// Export providers to be used in the feature -// It's not common to export providers outside of the presentation layer -// This file is useful for views that need to access multiple providers without -// importing them individually diff --git a/bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/viewmodels/viewmodels.dart b/bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/viewmodels/viewmodels.dart deleted file mode 100644 index 9747545..0000000 --- a/bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/viewmodels/viewmodels.dart +++ /dev/null @@ -1,4 +0,0 @@ -// Export states to be used in the feature -// It's not common to export states outside of the presentation layer -// This file is useful for views that need to access multiple states without -// importing them individually diff --git a/bin/bricks/mason.yaml b/bin/bricks/mason.yaml deleted file mode 100644 index 2a0a48b..0000000 --- a/bin/bricks/mason.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# Register bricks which can be consumed via the Mason CLI. -# Run "mason get" to install all registered bricks. -# To learn more, visit https://docs.brickhub.dev. -bricks: - # Bricks can be imported via version constraint from a registry. - # Uncomment the following line to import the "hello" brick from BrickHub. - # hello: 0.1.0+2 - # Bricks can also be imported via remote git url. - # Uncomment the following lines to import the "widget" brick from git. - # widget: - # git: - # url: https://github.com/felangel/mason.git - # path: bricks/widget - feature_module: - path: ./feature_module - base_project: - path: ./base_project \ No newline at end of file diff --git a/bin/bricks/base_project/__brick__/.gitignore b/bricks/base_project/__brick__/.gitignore similarity index 100% rename from bin/bricks/base_project/__brick__/.gitignore rename to bricks/base_project/__brick__/.gitignore diff --git a/bricks/base_project/__brick__/README.md b/bricks/base_project/__brick__/README.md new file mode 100644 index 0000000..94abfa8 --- /dev/null +++ b/bricks/base_project/__brick__/README.md @@ -0,0 +1,45 @@ +## {{project_name.snakeCase()}} + +## Starter Dependencies + +- [flutter_riverpod](https://pub.dev/packages/flutter_riverpod) +- [dart_mappable](https://pub.dev/packages/dart_mappable) +- [go_router](https://pub.dev/packages/go_router) +- [riverpod_annotation](https://pub.dev/packages/riverpod_annotation) +- [build_runner](https://pub.dev/packages/build_runner) +- [flutter_lints](https://pub.dev/packages/flutter_lints) +- [dart_mappable_builder](https://pub.dev/packages/dart_mappable_builder) +- [riverpod_generator](https://pub.dev/packages/riverpod_generator) + + +## Architecture & Folder Structure ๐Ÿ—๏ธ + +This project follows **Clean Architecture** principles and implements the **MVVM (Model-View-ViewModel)** pattern for the presentation layer. + +Inside the `lib/` directory, the code is structured by features (`lib/features/`). Each feature contains three main layers: + +- **Domain Layer (`domain/`)**: Contains your core business rules (Entities) and Interfaces (Repositories). It is independent of any external packages. +- **Data Layer (`data/`)**: Implements the repositories defined in the domain layer. Handles data sources (APIs, Local DBs) and mapping to Data Models using `dart_mappable`. +- **Presentation Layer (`presentation/`)**: Contains the UI widgets, screens, and ViewModels (using Riverpod) that manage the state of the views. + +### Generating Features + +To quickly scaffold a new feature with this exact architecture, run: + +```sh +xdevs_cli feature +# OR +xdevs_cli f +``` + +Example: `xdevs_cli feature auth` will generate `lib/features/auth` with the domain, data, and presentation folders ready to go! + +## Getting Started ๐Ÿš€ + +1. Install dependencies: `flutter pub get` +2. Run code generation for Data Models and Riverpod Providers: + ```sh + dart run build_runner build -d + ``` +3. Run the app: `flutter run` +Happy coding! ๐Ÿš€ \ No newline at end of file diff --git a/bin/bricks/base_project/__brick__/assets/.gitkeep b/bricks/base_project/__brick__/assets/.gitkeep similarity index 100% rename from bin/bricks/base_project/__brick__/assets/.gitkeep rename to bricks/base_project/__brick__/assets/.gitkeep diff --git a/bin/bricks/base_project/__brick__/lib/core/constants/strings.dart b/bricks/base_project/__brick__/lib/core/constants/strings.dart similarity index 100% rename from bin/bricks/base_project/__brick__/lib/core/constants/strings.dart rename to bricks/base_project/__brick__/lib/core/constants/strings.dart diff --git a/bin/bricks/base_project/__brick__/lib/core/di/.gitkeep b/bricks/base_project/__brick__/lib/core/di/.gitkeep similarity index 100% rename from bin/bricks/base_project/__brick__/lib/core/di/.gitkeep rename to bricks/base_project/__brick__/lib/core/di/.gitkeep diff --git a/bin/bricks/base_project/__brick__/lib/core/errors/.gitkeep b/bricks/base_project/__brick__/lib/core/errors/.gitkeep similarity index 100% rename from bin/bricks/base_project/__brick__/lib/core/errors/.gitkeep rename to bricks/base_project/__brick__/lib/core/errors/.gitkeep diff --git a/bin/bricks/base_project/__brick__/lib/core/network/.gitkeep b/bricks/base_project/__brick__/lib/core/network/.gitkeep similarity index 100% rename from bin/bricks/base_project/__brick__/lib/core/network/.gitkeep rename to bricks/base_project/__brick__/lib/core/network/.gitkeep diff --git a/bin/bricks/base_project/__brick__/lib/core/storage/.gitkeep b/bricks/base_project/__brick__/lib/core/storage/.gitkeep similarity index 100% rename from bin/bricks/base_project/__brick__/lib/core/storage/.gitkeep rename to bricks/base_project/__brick__/lib/core/storage/.gitkeep diff --git a/bin/bricks/base_project/__brick__/lib/core/themes/app_theme.dart b/bricks/base_project/__brick__/lib/core/themes/app_theme.dart similarity index 100% rename from bin/bricks/base_project/__brick__/lib/core/themes/app_theme.dart rename to bricks/base_project/__brick__/lib/core/themes/app_theme.dart diff --git a/bin/bricks/base_project/__brick__/lib/core/utils/.gitkeep b/bricks/base_project/__brick__/lib/core/utils/.gitkeep similarity index 100% rename from bin/bricks/base_project/__brick__/lib/core/utils/.gitkeep rename to bricks/base_project/__brick__/lib/core/utils/.gitkeep diff --git a/bin/bricks/base_project/__brick__/lib/features/app/app.dart b/bricks/base_project/__brick__/lib/features/app/app.dart similarity index 100% rename from bin/bricks/base_project/__brick__/lib/features/app/app.dart rename to bricks/base_project/__brick__/lib/features/app/app.dart diff --git a/bin/bricks/base_project/__brick__/lib/features/app/app_router.dart b/bricks/base_project/__brick__/lib/features/app/app_router.dart similarity index 100% rename from bin/bricks/base_project/__brick__/lib/features/app/app_router.dart rename to bricks/base_project/__brick__/lib/features/app/app_router.dart diff --git a/bricks/base_project/__brick__/lib/features/sample/data/data.dart b/bricks/base_project/__brick__/lib/features/sample/data/data.dart new file mode 100644 index 0000000..6a4429d --- /dev/null +++ b/bricks/base_project/__brick__/lib/features/sample/data/data.dart @@ -0,0 +1,7 @@ +/// Data layer for Sample feature. +/// +/// Export all datasources, models, and repository implementations here. + +// export 'datasources/sample_datasource.dart'; +// export 'models/sample_model.dart'; +// export 'repositories_impl/sample_repository_impl.dart'; diff --git a/bin/bricks/base_project/__brick__/lib/features/sample/data/datasources/sample_datasource.dart b/bricks/base_project/__brick__/lib/features/sample/data/datasources/sample_datasource.dart similarity index 100% rename from bin/bricks/base_project/__brick__/lib/features/sample/data/datasources/sample_datasource.dart rename to bricks/base_project/__brick__/lib/features/sample/data/datasources/sample_datasource.dart diff --git a/bin/bricks/base_project/__brick__/lib/features/sample/data/models/online_data.dart b/bricks/base_project/__brick__/lib/features/sample/data/models/online_data.dart similarity index 100% rename from bin/bricks/base_project/__brick__/lib/features/sample/data/models/online_data.dart rename to bricks/base_project/__brick__/lib/features/sample/data/models/online_data.dart diff --git a/bin/bricks/base_project/__brick__/lib/features/sample/data/repositories_impl/online_sample_data_repository.dart b/bricks/base_project/__brick__/lib/features/sample/data/repositories_impl/online_sample_data_repository.dart similarity index 100% rename from bin/bricks/base_project/__brick__/lib/features/sample/data/repositories_impl/online_sample_data_repository.dart rename to bricks/base_project/__brick__/lib/features/sample/data/repositories_impl/online_sample_data_repository.dart diff --git a/bricks/base_project/__brick__/lib/features/sample/domain/domain.dart b/bricks/base_project/__brick__/lib/features/sample/domain/domain.dart new file mode 100644 index 0000000..2b0bbcd --- /dev/null +++ b/bricks/base_project/__brick__/lib/features/sample/domain/domain.dart @@ -0,0 +1,8 @@ +/// Domain layer for Sample feature. +/// +/// Export all entities, abstract repositories, and use cases here. + +export 'usecases/get_sample_data_usecase.dart'; + +// export 'entities/sample_entity.dart'; +// export 'repositories/sample_repository.dart'; diff --git a/bin/bricks/base_project/__brick__/lib/features/sample/domain/entities/sample_data.dart b/bricks/base_project/__brick__/lib/features/sample/domain/entities/sample_data.dart similarity index 100% rename from bin/bricks/base_project/__brick__/lib/features/sample/domain/entities/sample_data.dart rename to bricks/base_project/__brick__/lib/features/sample/domain/entities/sample_data.dart diff --git a/bin/bricks/base_project/__brick__/lib/features/sample/domain/repositories/sample_data_repository.dart b/bricks/base_project/__brick__/lib/features/sample/domain/repositories/sample_data_repository.dart similarity index 100% rename from bin/bricks/base_project/__brick__/lib/features/sample/domain/repositories/sample_data_repository.dart rename to bricks/base_project/__brick__/lib/features/sample/domain/repositories/sample_data_repository.dart diff --git a/bin/bricks/base_project/__brick__/lib/features/sample/domain/usecases/.gitkeep b/bricks/base_project/__brick__/lib/features/sample/domain/usecases/.gitkeep similarity index 100% rename from bin/bricks/base_project/__brick__/lib/features/sample/domain/usecases/.gitkeep rename to bricks/base_project/__brick__/lib/features/sample/domain/usecases/.gitkeep diff --git a/bricks/base_project/__brick__/lib/features/sample/domain/usecases/get_sample_data_usecase.dart b/bricks/base_project/__brick__/lib/features/sample/domain/usecases/get_sample_data_usecase.dart new file mode 100644 index 0000000..c6f87b4 --- /dev/null +++ b/bricks/base_project/__brick__/lib/features/sample/domain/usecases/get_sample_data_usecase.dart @@ -0,0 +1,8 @@ +class GetSampleDataUseCase { + const GetSampleDataUseCase(); + + Future call() async { + // Implement use case logic here + await Future.delayed(const Duration(seconds: 1)); + } +} diff --git a/bricks/base_project/__brick__/lib/features/sample/presentation/providers/providers.dart b/bricks/base_project/__brick__/lib/features/sample/presentation/providers/providers.dart new file mode 100644 index 0000000..6f95099 --- /dev/null +++ b/bricks/base_project/__brick__/lib/features/sample/presentation/providers/providers.dart @@ -0,0 +1 @@ +export 'sample_provider.dart'; diff --git a/bin/bricks/base_project/__brick__/lib/features/sample/presentation/providers/sample_provider.dart b/bricks/base_project/__brick__/lib/features/sample/presentation/providers/sample_provider.dart similarity index 69% rename from bin/bricks/base_project/__brick__/lib/features/sample/presentation/providers/sample_provider.dart rename to bricks/base_project/__brick__/lib/features/sample/presentation/providers/sample_provider.dart index 3b895cb..6a3b710 100644 --- a/bin/bricks/base_project/__brick__/lib/features/sample/presentation/providers/sample_provider.dart +++ b/bricks/base_project/__brick__/lib/features/sample/presentation/providers/sample_provider.dart @@ -2,17 +2,18 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:{{project_name.snakeCase()}}/features/sample/data/data.dart'; import 'package:{{project_name.snakeCase()}}/features/sample/domain/domain.dart'; -import 'package:{{project_name.snakeCase()}}/features/sample/presentation/viewmodels/sample_state.dart'; +import 'package:{{project_name.snakeCase()}}/features/sample/presentation/viewmodels/sample_viewmodel.dart'; part 'sample_provider.g.dart'; @riverpod class Sample extends _$Sample { @override - FutureOr build() async { + FutureOr build() async { final repository = ref.read(sampleDataRepositoryProvider); - final sampleData = await repository.getSampleData(); - return SampleState(sampleData: sampleData.data); + // You can use repository data to populate the viewmodel + // final sampleData = await repository.getSampleData(); + return const SampleViewModel(); } } diff --git a/bricks/base_project/__brick__/lib/features/sample/presentation/viewmodels/sample_viewmodel.dart b/bricks/base_project/__brick__/lib/features/sample/presentation/viewmodels/sample_viewmodel.dart new file mode 100644 index 0000000..a3b19b0 --- /dev/null +++ b/bricks/base_project/__brick__/lib/features/sample/presentation/viewmodels/sample_viewmodel.dart @@ -0,0 +1,12 @@ +import 'package:dart_mappable/dart_mappable.dart'; + +part 'sample_viewmodel.mapper.dart'; + +@MappableClass() +class SampleViewModel with SampleViewModelMappable { + const SampleViewModel({ + this.isLoading = false, + }); + + final bool isLoading; +} diff --git a/bricks/base_project/__brick__/lib/features/sample/presentation/viewmodels/viewmodels.dart b/bricks/base_project/__brick__/lib/features/sample/presentation/viewmodels/viewmodels.dart new file mode 100644 index 0000000..53f2a76 --- /dev/null +++ b/bricks/base_project/__brick__/lib/features/sample/presentation/viewmodels/viewmodels.dart @@ -0,0 +1 @@ +export 'sample_viewmodel.dart'; diff --git a/bin/bricks/base_project/__brick__/lib/features/sample/presentation/views/sample_screen.dart b/bricks/base_project/__brick__/lib/features/sample/presentation/views/sample_screen.dart similarity index 78% rename from bin/bricks/base_project/__brick__/lib/features/sample/presentation/views/sample_screen.dart rename to bricks/base_project/__brick__/lib/features/sample/presentation/views/sample_screen.dart index 98bbd4e..d357d7c 100644 --- a/bin/bricks/base_project/__brick__/lib/features/sample/presentation/views/sample_screen.dart +++ b/bricks/base_project/__brick__/lib/features/sample/presentation/views/sample_screen.dart @@ -12,7 +12,11 @@ class SampleScreen extends ConsumerWidget { return Scaffold( appBar: AppBar(title: const Text('Sample Screen')), body: sampleState.when( - data: (data) => Center(child: Text(data.sampleData)), + data: (viewModel) => Center( + child: viewModel.isLoading + ? const CircularProgressIndicator() + : const Text('Sample Screen Data'), + ), loading: () => const Center(child: CircularProgressIndicator()), error: (error, _) => Center(child: Text('Error: $error')), ), diff --git a/bin/bricks/base_project/__brick__/lib/features/sample/presentation/widgets/widgets.dart b/bricks/base_project/__brick__/lib/features/sample/presentation/widgets/widgets.dart similarity index 100% rename from bin/bricks/base_project/__brick__/lib/features/sample/presentation/widgets/widgets.dart rename to bricks/base_project/__brick__/lib/features/sample/presentation/widgets/widgets.dart diff --git a/bin/bricks/base_project/__brick__/lib/features/shared/helpers/.gitkeep b/bricks/base_project/__brick__/lib/features/shared/helpers/.gitkeep similarity index 100% rename from bin/bricks/base_project/__brick__/lib/features/shared/helpers/.gitkeep rename to bricks/base_project/__brick__/lib/features/shared/helpers/.gitkeep diff --git a/bin/bricks/base_project/__brick__/lib/features/shared/shared.dart b/bricks/base_project/__brick__/lib/features/shared/shared.dart similarity index 100% rename from bin/bricks/base_project/__brick__/lib/features/shared/shared.dart rename to bricks/base_project/__brick__/lib/features/shared/shared.dart diff --git a/bin/bricks/base_project/__brick__/lib/features/shared/widgets/.gitkeep b/bricks/base_project/__brick__/lib/features/shared/widgets/.gitkeep similarity index 100% rename from bin/bricks/base_project/__brick__/lib/features/shared/widgets/.gitkeep rename to bricks/base_project/__brick__/lib/features/shared/widgets/.gitkeep diff --git a/bin/bricks/base_project/__brick__/lib/main.dart b/bricks/base_project/__brick__/lib/main.dart similarity index 100% rename from bin/bricks/base_project/__brick__/lib/main.dart rename to bricks/base_project/__brick__/lib/main.dart diff --git a/bin/bricks/base_project/__brick__/pubspec.yaml b/bricks/base_project/__brick__/pubspec.yaml similarity index 100% rename from bin/bricks/base_project/__brick__/pubspec.yaml rename to bricks/base_project/__brick__/pubspec.yaml diff --git a/bin/bricks/base_project/brick.yaml b/bricks/base_project/brick.yaml similarity index 100% rename from bin/bricks/base_project/brick.yaml rename to bricks/base_project/brick.yaml diff --git a/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/data.dart b/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/data.dart new file mode 100644 index 0000000..fecc6cc --- /dev/null +++ b/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/data.dart @@ -0,0 +1,7 @@ +/// Data layer for {{feature_name.pascalCase()}} feature. +/// +/// Export all datasources, models, and repository implementations here. + +// export 'datasources/{{feature_name.snakeCase()}}_datasource.dart'; +// export 'models/{{feature_name.snakeCase()}}_model.dart'; +// export 'repositories_impl/{{feature_name.snakeCase()}}_repository_impl.dart'; diff --git a/bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/datasources/.gitkeep b/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/datasources/.gitkeep similarity index 100% rename from bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/datasources/.gitkeep rename to bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/datasources/.gitkeep diff --git a/bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/models/.gitkeep b/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/models/.gitkeep similarity index 100% rename from bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/models/.gitkeep rename to bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/models/.gitkeep diff --git a/bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/repositories_impl/.gitkeep b/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/repositories_impl/.gitkeep similarity index 100% rename from bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/repositories_impl/.gitkeep rename to bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/data/repositories_impl/.gitkeep diff --git a/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/domain/domain.dart b/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/domain/domain.dart new file mode 100644 index 0000000..36ea853 --- /dev/null +++ b/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/domain/domain.dart @@ -0,0 +1,8 @@ +/// Domain layer for {{feature_name.pascalCase()}} feature. +/// +/// Export all entities, abstract repositories, and use cases here. + +export 'usecases/get_{{feature_name.snakeCase()}}_data_usecase.dart'; + +// export 'entities/{{feature_name.snakeCase()}}_entity.dart'; +// export 'repositories/{{feature_name.snakeCase()}}_repository.dart'; diff --git a/bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/domain/entities/.gitkeep b/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/domain/entities/.gitkeep similarity index 100% rename from bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/domain/entities/.gitkeep rename to bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/domain/entities/.gitkeep diff --git a/bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/domain/repositories/.gitkeep b/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/domain/repositories/.gitkeep similarity index 100% rename from bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/domain/repositories/.gitkeep rename to bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/domain/repositories/.gitkeep diff --git a/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/domain/usecases/get_{{feature_name.snakeCase()}}_data_usecase.dart b/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/domain/usecases/get_{{feature_name.snakeCase()}}_data_usecase.dart new file mode 100644 index 0000000..94ac397 --- /dev/null +++ b/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/domain/usecases/get_{{feature_name.snakeCase()}}_data_usecase.dart @@ -0,0 +1,8 @@ +class Get{{feature_name.pascalCase()}}DataUseCase { + const Get{{feature_name.pascalCase()}}DataUseCase(); + + Future call() async { + // Implement use case logic here + await Future.delayed(const Duration(seconds: 1)); + } +} diff --git a/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/providers/providers.dart b/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/providers/providers.dart new file mode 100644 index 0000000..271cb62 --- /dev/null +++ b/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/providers/providers.dart @@ -0,0 +1 @@ +export '{{feature_name.snakeCase()}}_provider.dart'; diff --git a/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/providers/{{feature_name.snakeCase()}}_provider.dart b/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/providers/{{feature_name.snakeCase()}}_provider.dart new file mode 100644 index 0000000..3fbdc67 --- /dev/null +++ b/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/providers/{{feature_name.snakeCase()}}_provider.dart @@ -0,0 +1,14 @@ +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import '../viewmodels/{{feature_name.snakeCase()}}_viewmodel.dart'; + +part '{{feature_name.snakeCase()}}_provider.g.dart'; + +@riverpod +class {{feature_name.pascalCase()}} extends _${{feature_name.pascalCase()}} { + @override + {{feature_name.pascalCase()}}ViewModel build() { + return const {{feature_name.pascalCase()}}ViewModel(); + } + + // Add business logic methods here +} diff --git a/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/viewmodels/viewmodels.dart b/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/viewmodels/viewmodels.dart new file mode 100644 index 0000000..4472b11 --- /dev/null +++ b/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/viewmodels/viewmodels.dart @@ -0,0 +1 @@ +export '{{feature_name.snakeCase()}}_viewmodel.dart'; diff --git a/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/viewmodels/{{feature_name.snakeCase()}}_viewmodel.dart b/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/viewmodels/{{feature_name.snakeCase()}}_viewmodel.dart new file mode 100644 index 0000000..bb454af --- /dev/null +++ b/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/viewmodels/{{feature_name.snakeCase()}}_viewmodel.dart @@ -0,0 +1,12 @@ +import 'package:dart_mappable/dart_mappable.dart'; + +part '{{feature_name.snakeCase()}}_viewmodel.mapper.dart'; + +@MappableClass() +class {{feature_name.pascalCase()}}ViewModel with {{feature_name.pascalCase()}}ViewModelMappable { + const {{feature_name.pascalCase()}}ViewModel({ + this.isLoading = false, + }); + + final bool isLoading; +} diff --git a/bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/views/{{feature_name.snakeCase()}}_screen.dart b/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/views/{{feature_name.snakeCase()}}_screen.dart similarity index 55% rename from bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/views/{{feature_name.snakeCase()}}_screen.dart rename to bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/views/{{feature_name.snakeCase()}}_screen.dart index c8f409d..afe04ad 100644 --- a/bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/views/{{feature_name.snakeCase()}}_screen.dart +++ b/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/views/{{feature_name.snakeCase()}}_screen.dart @@ -1,14 +1,22 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../providers/{{feature_name.snakeCase()}}_provider.dart'; + class {{feature_name.pascalCase()}}Screen extends ConsumerWidget { const {{feature_name.pascalCase()}}Screen({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { + final viewModel = ref.watch({{feature_name.camelCase()}}Provider); + return Scaffold( appBar: AppBar(title: const Text('{{feature_name.pascalCase()}}')), - body: Center(child: Text('{{feature_name.pascalCase()}} Screen')), + body: Center( + child: viewModel.isLoading + ? const CircularProgressIndicator() + : const Text('{{feature_name.pascalCase()}} Screen'), + ), ); } } \ No newline at end of file diff --git a/bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/widgets/widgets.dart b/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/widgets/widgets.dart similarity index 100% rename from bin/bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/widgets/widgets.dart rename to bricks/feature_module/__brick__/lib/features/{{feature_name.snakeCase()}}/presentation/widgets/widgets.dart diff --git a/bin/bricks/feature_module/brick.yaml b/bricks/feature_module/brick.yaml similarity index 100% rename from bin/bricks/feature_module/brick.yaml rename to bricks/feature_module/brick.yaml diff --git a/bricks/mason.yaml b/bricks/mason.yaml new file mode 100644 index 0000000..2d68cf1 --- /dev/null +++ b/bricks/mason.yaml @@ -0,0 +1,5 @@ +bricks: + feature_module: + path: ./feature_module + base_project: + path: ./base_project \ No newline at end of file diff --git a/lib/src/bundles/base_project_bundle.dart b/lib/src/bundles/base_project_bundle.dart new file mode 100644 index 0000000..d74ef4a --- /dev/null +++ b/lib/src/bundles/base_project_bundle.dart @@ -0,0 +1,200 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, implicit_dynamic_list_literal, implicit_dynamic_map_literal, inference_failure_on_collection_literal + +import 'package:mason/mason.dart'; + +final baseProjectBundle = MasonBundle.fromJson({ + "files": [ + { + "path": ".gitignore", + "data": + "LmRhcnRfdG9vbC8KLmlkZWEvCi52c2NvZGUvCmJ1aWxkLwoucGFja2FnZXMKLmZsdXR0ZXItcGx1Z2lucwouZmx1dHRlci1wbHVnaW5zLWRlcGVuZGVuY2llcw==", + "type": "text", + }, + {"path": "assets/.gitkeep", "data": "", "type": "text"}, + { + "path": "lib/core/constants/strings.dart", + "data": + "Y29uc3QgYXBwTmFtZSA9ICd7e3Byb2plY3RfbmFtZS5wYXNjYWxDYXNlKCl9fSc7Cg==", + "type": "text", + }, + {"path": "lib/core/di/.gitkeep", "data": "", "type": "text"}, + {"path": "lib/core/errors/.gitkeep", "data": "", "type": "text"}, + {"path": "lib/core/network/.gitkeep", "data": "", "type": "text"}, + {"path": "lib/core/storage/.gitkeep", "data": "", "type": "text"}, + { + "path": "lib/core/themes/app_theme.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CgpjbGFzcyBBcHBUaGVtZSB7CiAgY29uc3QgQXBwVGhlbWUuXygpOwoKICBzdGF0aWMgVGhlbWVEYXRhIGdldCBsaWdodCB7CiAgICByZXR1cm4gVGhlbWVEYXRhKAogICAgICBjb2xvclNjaGVtZTogQ29sb3JTY2hlbWUuZnJvbVNlZWQoc2VlZENvbG9yOiBDb2xvcnMudGVhbCksCiAgICAgIHVzZU1hdGVyaWFsMzogdHJ1ZSwKICAgICk7CiAgfQp9Cg==", + "type": "text", + }, + {"path": "lib/core/utils/.gitkeep", "data": "", "type": "text"}, + { + "path": "lib/features/app/app.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CgppbXBvcnQgJ2FwcF9yb3V0ZXIuZGFydCc7CmltcG9ydCAnLi4vLi4vY29yZS90aGVtZXMvYXBwX3RoZW1lLmRhcnQnOwoKY2xhc3MgQXBwIGV4dGVuZHMgU3RhdGVsZXNzV2lkZ2V0IHsKICBjb25zdCBBcHAoe3N1cGVyLmtleX0pOwoKICBAb3ZlcnJpZGUKICBXaWRnZXQgYnVpbGQoQnVpbGRDb250ZXh0IGNvbnRleHQpIHsKICAgIHJldHVybiBNYXRlcmlhbEFwcC5yb3V0ZXIoCiAgICAgIGRlYnVnU2hvd0NoZWNrZWRNb2RlQmFubmVyOiBmYWxzZSwKICAgICAgcm91dGVyQ29uZmlnOiBhcHBSb3V0ZXIsCiAgICAgIHRoZW1lOiBBcHBUaGVtZS5saWdodCwKICAgICk7CiAgfQp9Cg==", + "type": "text", + }, + { + "path": "lib/features/app/app_router.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmdvX3JvdXRlci9nb19yb3V0ZXIuZGFydCc7CgppbXBvcnQgJ3BhY2thZ2U6e3twcm9qZWN0X25hbWUuc25ha2VDYXNlKCl9fS9mZWF0dXJlcy9mZWF0dXJlc19zY3JlZW5zLmRhcnQnOwoKZmluYWwgYXBwUm91dGVyID0gR29Sb3V0ZXIoCiAgcm91dGVzOiBbR29Sb3V0ZShwYXRoOiAnLycsIGJ1aWxkZXI6IChfLCBfKSA9PiBjb25zdCBTYW1wbGVTY3JlZW4oKSldLAopOwo=", + "type": "text", + }, + { + "path": "lib/features/sample/data/data.dart", + "data": + "Ly8vIERhdGEgbGF5ZXIgZm9yIFNhbXBsZSBmZWF0dXJlLgovLy8gCi8vLyBFeHBvcnQgYWxsIGRhdGFzb3VyY2VzLCBtb2RlbHMsIGFuZCByZXBvc2l0b3J5IGltcGxlbWVudGF0aW9ucyBoZXJlLgoKLy8gZXhwb3J0ICdkYXRhc291cmNlcy9zYW1wbGVfZGF0YXNvdXJjZS5kYXJ0JzsKLy8gZXhwb3J0ICdtb2RlbHMvc2FtcGxlX21vZGVsLmRhcnQnOwovLyBleHBvcnQgJ3JlcG9zaXRvcmllc19pbXBsL3NhbXBsZV9yZXBvc2l0b3J5X2ltcGwuZGFydCc7Cg==", + "type": "text", + }, + { + "path": "lib/features/sample/data/datasources/sample_datasource.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOnt7cHJvamVjdF9uYW1lLnNuYWtlQ2FzZSgpfX0vZmVhdHVyZXMvc2FtcGxlL2RhdGEvZGF0YS5kYXJ0JzsKCmNsYXNzIFNhbXBsZURhdGFzb3VyY2UgewogIC8vIFNpbXVsYXRlIGZldGNoaW5nIGRhdGEgZnJvbSBhbiBBUEkgb3IgZGF0YWJhc2UKICBGdXR1cmU8T25saW5lRGF0YT4gZmV0Y2hPbmxpbmVEYXRhKCkgYXN5bmMgewogICAgYXdhaXQgRnV0dXJlPHZvaWQ+LmRlbGF5ZWQoCiAgICAgIGNvbnN0IER1cmF0aW9uKHNlY29uZHM6IDIpLAogICAgKTsgLy8gU2ltdWxhdGUgbmV0d29yayBkZWxheQogICAgcmV0dXJuIE9ubGluZURhdGEoZGF0YTogJ01hZGUgd2l0aCDinaTvuI8gYnkgWGl0b0RldicpOwogIH0KfQo=", + "type": "text", + }, + { + "path": "lib/features/sample/data/models/online_data.dart", + "data": + "Y2xhc3MgT25saW5lRGF0YSB7CiAgT25saW5lRGF0YSh7cmVxdWlyZWQgdGhpcy5kYXRhfSk7CgogIGZpbmFsIFN0cmluZyBkYXRhOwp9Cg==", + "type": "text", + }, + { + "path": + "lib/features/sample/data/repositories_impl/online_sample_data_repository.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOnt7cHJvamVjdF9uYW1lLnNuYWtlQ2FzZSgpfX0vZmVhdHVyZXMvc2FtcGxlL2RhdGEvZGF0YS5kYXJ0JzsKaW1wb3J0ICdwYWNrYWdlOnt7cHJvamVjdF9uYW1lLnNuYWtlQ2FzZSgpfX0vZmVhdHVyZXMvc2FtcGxlL2RvbWFpbi9kb21haW4uZGFydCc7CgovLyBDb3VsZCBiZSBuYW1lZCBTYW1wbGVEYXRhUmVwb3NpdG9yeUltcGwgaWYgaXQncyB0aGUgb25seSBpbXBsZW1lbnRhdGlvbgpjbGFzcyBPbmxpbmVTYW1wbGVEYXRhUmVwb3NpdG9yeSBpbXBsZW1lbnRzIFNhbXBsZURhdGFSZXBvc2l0b3J5IHsKICBPbmxpbmVTYW1wbGVEYXRhUmVwb3NpdG9yeShbU2FtcGxlRGF0YXNvdXJjZT8gZGF0YXNvdXJjZV0pCiAgICA6IGRhdGFzb3VyY2UgPSBkYXRhc291cmNlID8/IFNhbXBsZURhdGFzb3VyY2UoKTsKCiAgZmluYWwgU2FtcGxlRGF0YXNvdXJjZSBkYXRhc291cmNlOwoKICBAb3ZlcnJpZGUKICBGdXR1cmU8U2FtcGxlRGF0YT4gZ2V0U2FtcGxlRGF0YSgpIGFzeW5jIHsKICAgIGZpbmFsIG9ubGluZURhdGEgPSBhd2FpdCBkYXRhc291cmNlLmZldGNoT25saW5lRGF0YSgpOwogICAgcmV0dXJuIFNhbXBsZURhdGEoZGF0YTogb25saW5lRGF0YS5kYXRhKTsKICB9Cn0K", + "type": "text", + }, + { + "path": "lib/features/sample/domain/domain.dart", + "data": + "Ly8vIERvbWFpbiBsYXllciBmb3IgU2FtcGxlIGZlYXR1cmUuCi8vLyAKLy8vIEV4cG9ydCBhbGwgZW50aXRpZXMsIGFic3RyYWN0IHJlcG9zaXRvcmllcywgYW5kIHVzZSBjYXNlcyBoZXJlLgoKZXhwb3J0ICd1c2VjYXNlcy9nZXRfc2FtcGxlX2RhdGFfdXNlY2FzZS5kYXJ0JzsKCi8vIGV4cG9ydCAnZW50aXRpZXMvc2FtcGxlX2VudGl0eS5kYXJ0JzsKLy8gZXhwb3J0ICdyZXBvc2l0b3JpZXMvc2FtcGxlX3JlcG9zaXRvcnkuZGFydCc7Cg==", + "type": "text", + }, + { + "path": "lib/features/sample/domain/entities/sample_data.dart", + "data": + "Y2xhc3MgU2FtcGxlRGF0YSB7CiAgU2FtcGxlRGF0YSh7cmVxdWlyZWQgdGhpcy5kYXRhfSk7CgogIGZpbmFsIFN0cmluZyBkYXRhOwp9Cg==", + "type": "text", + }, + { + "path": + "lib/features/sample/domain/repositories/sample_data_repository.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOnt7cHJvamVjdF9uYW1lLnNuYWtlQ2FzZSgpfX0vZmVhdHVyZXMvc2FtcGxlL2RvbWFpbi9lbnRpdGllcy9zYW1wbGVfZGF0YS5kYXJ0JzsKCmFic3RyYWN0IGNsYXNzIFNhbXBsZURhdGFSZXBvc2l0b3J5IHsKICBGdXR1cmU8U2FtcGxlRGF0YT4gZ2V0U2FtcGxlRGF0YSgpOwp9Cg==", + "type": "text", + }, + { + "path": "lib/features/sample/domain/usecases/.gitkeep", + "data": "", + "type": "text", + }, + { + "path": + "lib/features/sample/domain/usecases/get_sample_data_usecase.dart", + "data": + "Y2xhc3MgR2V0U2FtcGxlRGF0YVVzZUNhc2UgewogIGNvbnN0IEdldFNhbXBsZURhdGFVc2VDYXNlKCk7CgogIEZ1dHVyZTx2b2lkPiBjYWxsKCkgYXN5bmMgewogICAgLy8gSW1wbGVtZW50IHVzZSBjYXNlIGxvZ2ljIGhlcmUKICAgIGF3YWl0IEZ1dHVyZS5kZWxheWVkKGNvbnN0IER1cmF0aW9uKHNlY29uZHM6IDEpKTsKICB9Cn0K", + "type": "text", + }, + { + "path": "lib/features/sample/presentation/providers/providers.dart", + "data": "ZXhwb3J0ICdzYW1wbGVfcHJvdmlkZXIuZGFydCc7Cg==", + "type": "text", + }, + { + "path": "lib/features/sample/presentation/providers/sample_provider.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOnJpdmVycG9kX2Fubm90YXRpb24vcml2ZXJwb2RfYW5ub3RhdGlvbi5kYXJ0JzsKCmltcG9ydCAncGFja2FnZTp7e3Byb2plY3RfbmFtZS5zbmFrZUNhc2UoKX19L2ZlYXR1cmVzL3NhbXBsZS9kYXRhL2RhdGEuZGFydCc7CmltcG9ydCAncGFja2FnZTp7e3Byb2plY3RfbmFtZS5zbmFrZUNhc2UoKX19L2ZlYXR1cmVzL3NhbXBsZS9kb21haW4vZG9tYWluLmRhcnQnOwppbXBvcnQgJ3BhY2thZ2U6e3twcm9qZWN0X25hbWUuc25ha2VDYXNlKCl9fS9mZWF0dXJlcy9zYW1wbGUvcHJlc2VudGF0aW9uL3ZpZXdtb2RlbHMvc2FtcGxlX3ZpZXdtb2RlbC5kYXJ0JzsKCnBhcnQgJ3NhbXBsZV9wcm92aWRlci5nLmRhcnQnOwoKQHJpdmVycG9kCmNsYXNzIFNhbXBsZSBleHRlbmRzIF8kU2FtcGxlIHsKICBAb3ZlcnJpZGUKICBGdXR1cmVPcjxTYW1wbGVWaWV3TW9kZWw+IGJ1aWxkKCkgYXN5bmMgewogICAgZmluYWwgcmVwb3NpdG9yeSA9IHJlZi5yZWFkKHNhbXBsZURhdGFSZXBvc2l0b3J5UHJvdmlkZXIpOwogICAgLy8gWW91IGNhbiB1c2UgcmVwb3NpdG9yeSBkYXRhIHRvIHBvcHVsYXRlIHRoZSB2aWV3bW9kZWwKICAgIC8vIGZpbmFsIHNhbXBsZURhdGEgPSBhd2FpdCByZXBvc2l0b3J5LmdldFNhbXBsZURhdGEoKTsKICAgIHJldHVybiBjb25zdCBTYW1wbGVWaWV3TW9kZWwoKTsKICB9Cn0KCkByaXZlcnBvZApTYW1wbGVEYXRhUmVwb3NpdG9yeSBzYW1wbGVEYXRhUmVwb3NpdG9yeShSZWYgcmVmKSB7CiAgcmV0dXJuIE9ubGluZVNhbXBsZURhdGFSZXBvc2l0b3J5KCk7Cn0K", + "type": "text", + }, + { + "path": + "lib/features/sample/presentation/viewmodels/sample_viewmodel.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmRhcnRfbWFwcGFibGUvZGFydF9tYXBwYWJsZS5kYXJ0JzsKCnBhcnQgJ3NhbXBsZV92aWV3bW9kZWwubWFwcGVyLmRhcnQnOwoKQE1hcHBhYmxlQ2xhc3MoKQpjbGFzcyBTYW1wbGVWaWV3TW9kZWwgd2l0aCBTYW1wbGVWaWV3TW9kZWxNYXBwYWJsZSB7CiAgY29uc3QgU2FtcGxlVmlld01vZGVsKHsKICAgIHRoaXMuaXNMb2FkaW5nID0gZmFsc2UsCiAgfSk7CgogIGZpbmFsIGJvb2wgaXNMb2FkaW5nOwp9Cg==", + "type": "text", + }, + { + "path": "lib/features/sample/presentation/viewmodels/viewmodels.dart", + "data": "ZXhwb3J0ICdzYW1wbGVfdmlld21vZGVsLmRhcnQnOwo=", + "type": "text", + }, + { + "path": "lib/features/sample/presentation/views/sample_screen.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CmltcG9ydCAncGFja2FnZTpmbHV0dGVyX3JpdmVycG9kL2ZsdXR0ZXJfcml2ZXJwb2QuZGFydCc7CgppbXBvcnQgJ3BhY2thZ2U6e3twcm9qZWN0X25hbWUuc25ha2VDYXNlKCl9fS9mZWF0dXJlcy9zYW1wbGUvcHJlc2VudGF0aW9uL3Byb3ZpZGVycy9wcm92aWRlcnMuZGFydCc7CgpjbGFzcyBTYW1wbGVTY3JlZW4gZXh0ZW5kcyBDb25zdW1lcldpZGdldCB7CiAgY29uc3QgU2FtcGxlU2NyZWVuKHtzdXBlci5rZXl9KTsKCiAgQG92ZXJyaWRlCiAgV2lkZ2V0IGJ1aWxkKEJ1aWxkQ29udGV4dCBjb250ZXh0LCBXaWRnZXRSZWYgcmVmKSB7CiAgICBmaW5hbCBzYW1wbGVTdGF0ZSA9IHJlZi53YXRjaChzYW1wbGVQcm92aWRlcik7CiAgICByZXR1cm4gU2NhZmZvbGQoCiAgICAgIGFwcEJhcjogQXBwQmFyKHRpdGxlOiBjb25zdCBUZXh0KCdTYW1wbGUgU2NyZWVuJykpLAogICAgICBib2R5OiBzYW1wbGVTdGF0ZS53aGVuKAogICAgICAgIGRhdGE6ICh2aWV3TW9kZWwpID0+IENlbnRlcigKICAgICAgICAgIGNoaWxkOiB2aWV3TW9kZWwuaXNMb2FkaW5nIAogICAgICAgICAgICA/IGNvbnN0IENpcmN1bGFyUHJvZ3Jlc3NJbmRpY2F0b3IoKQogICAgICAgICAgICA6IGNvbnN0IFRleHQoJ1NhbXBsZSBTY3JlZW4gRGF0YScpLAogICAgICAgICksCiAgICAgICAgbG9hZGluZzogKCkgPT4gY29uc3QgQ2VudGVyKGNoaWxkOiBDaXJjdWxhclByb2dyZXNzSW5kaWNhdG9yKCkpLAogICAgICAgIGVycm9yOiAoZXJyb3IsIF8pID0+IENlbnRlcihjaGlsZDogVGV4dCgnRXJyb3I6ICRlcnJvcicpKSwKICAgICAgKSwKICAgICk7CiAgfQp9Cg==", + "type": "text", + }, + { + "path": "lib/features/sample/presentation/widgets/widgets.dart", + "data": + "Ly8gRXhwb3J0IHdpZGdldHMgdG8gYmUgdXNlZCBpbiB0aGUgZmVhdHVyZQovLyBJdCdzIG5vdCBjb21tb24gdG8gZXhwb3J0IGludGVybmFsIHdpZGdldHMgb3V0c2lkZSBvZiB0aGUgcHJlc2VudGF0aW9uIGxheWVyCi8vIFRoaXMgZmlsZSBpcyB1c2VmdWwgZm9yIHZpZXdzIHRoYXQgbmVlZCB0byBhY2Nlc3MgbXVsdGlwbGUgd2lkZ2V0cyB3aXRob3V0Ci8vIGltcG9ydGluZyB0aGVtIGluZGl2aWR1YWxseQo=", + "type": "text", + }, + { + "path": "lib/features/shared/helpers/.gitkeep", + "data": "", + "type": "text", + }, + { + "path": "lib/features/shared/shared.dart", + "data": + "Ly8gRXhwb3J0IHdpZGdldHMgYW5kIGhlbHBlcnMgdGhhdCBhcmUgc2hhcmVkIGFjcm9zcyBmZWF0dXJlcwo=", + "type": "text", + }, + { + "path": "lib/features/shared/widgets/.gitkeep", + "data": "Ly8gRXhwb3J0IHNoYXJlZCB3aWRnZXRzCg==", + "type": "text", + }, + { + "path": "lib/main.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CmltcG9ydCAncGFja2FnZTpmbHV0dGVyX3JpdmVycG9kL2ZsdXR0ZXJfcml2ZXJwb2QuZGFydCc7CgppbXBvcnQgJ2ZlYXR1cmVzL2FwcC9hcHAuZGFydCc7Cgp2b2lkIG1haW4oKSB7CiAgcnVuQXBwKFByb3ZpZGVyU2NvcGUoY2hpbGQ6IGNvbnN0IEFwcCgpKSk7Cn0K", + "type": "text", + }, + { + "path": "pubspec.yaml", + "data": + "bmFtZToge3twcm9qZWN0X25hbWUuc25ha2VDYXNlKCl9fQpkZXNjcmlwdGlvbjogQSBGbHV0dGVyIGFwcCBnZW5lcmF0ZWQgd2l0aCBzbm93X2NsaS4KcHVibGlzaF90bzogbm9uZQoKdmVyc2lvbjogMC4xLjArMQoKZW52aXJvbm1lbnQ6CiAgc2RrOiBeMy4xMS4wCgpkZXBlbmRlbmNpZXM6CiAgZmx1dHRlcjoKICAgIHNkazogZmx1dHRlcgoKZGV2X2RlcGVuZGVuY2llczoKICBmbHV0dGVyX3Rlc3Q6CiAgICBzZGs6IGZsdXR0ZXIKCmZsdXR0ZXI6CiAgdXNlcy1tYXRlcmlhbC1kZXNpZ246IHRydWU=", + "type": "text", + }, + { + "path": "README.md", + "data": + "IyMge3twcm9qZWN0X25hbWUuc25ha2VDYXNlKCl9fQoKIyMgU3RhcnRlciBEZXBlbmRlbmNpZXMKCi0gW2ZsdXR0ZXJfcml2ZXJwb2RdKGh0dHBzOi8vcHViLmRldi9wYWNrYWdlcy9mbHV0dGVyX3JpdmVycG9kKQotIFtkYXJ0X21hcHBhYmxlXShodHRwczovL3B1Yi5kZXYvcGFja2FnZXMvZGFydF9tYXBwYWJsZSkKLSBbZ29fcm91dGVyXShodHRwczovL3B1Yi5kZXYvcGFja2FnZXMvZ29fcm91dGVyKQotIFtyaXZlcnBvZF9hbm5vdGF0aW9uXShodHRwczovL3B1Yi5kZXYvcGFja2FnZXMvcml2ZXJwb2RfYW5ub3RhdGlvbikKLSBbYnVpbGRfcnVubmVyXShodHRwczovL3B1Yi5kZXYvcGFja2FnZXMvYnVpbGRfcnVubmVyKQotIFtmbHV0dGVyX2xpbnRzXShodHRwczovL3B1Yi5kZXYvcGFja2FnZXMvZmx1dHRlcl9saW50cykKLSBbZGFydF9tYXBwYWJsZV9idWlsZGVyXShodHRwczovL3B1Yi5kZXYvcGFja2FnZXMvZGFydF9tYXBwYWJsZV9idWlsZGVyKQotIFtyaXZlcnBvZF9nZW5lcmF0b3JdKGh0dHBzOi8vcHViLmRldi9wYWNrYWdlcy9yaXZlcnBvZF9nZW5lcmF0b3IpCgoKIyMgQXJjaGl0ZWN0dXJlICYgRm9sZGVyIFN0cnVjdHVyZSDwn4+X77iPCgpUaGlzIHByb2plY3QgZm9sbG93cyAqKkNsZWFuIEFyY2hpdGVjdHVyZSoqIHByaW5jaXBsZXMgYW5kIGltcGxlbWVudHMgdGhlICoqTVZWTSAoTW9kZWwtVmlldy1WaWV3TW9kZWwpKiogcGF0dGVybiBmb3IgdGhlIHByZXNlbnRhdGlvbiBsYXllci4KCkluc2lkZSB0aGUgYGxpYi9gIGRpcmVjdG9yeSwgdGhlIGNvZGUgaXMgc3RydWN0dXJlZCBieSBmZWF0dXJlcyAoYGxpYi9mZWF0dXJlcy9gKS4gRWFjaCBmZWF0dXJlIGNvbnRhaW5zIHRocmVlIG1haW4gbGF5ZXJzOgoKLSAqKkRvbWFpbiBMYXllciAoYGRvbWFpbi9gKSoqOiBDb250YWlucyB5b3VyIGNvcmUgYnVzaW5lc3MgcnVsZXMgKEVudGl0aWVzKSBhbmQgSW50ZXJmYWNlcyAoUmVwb3NpdG9yaWVzKS4gSXQgaXMgaW5kZXBlbmRlbnQgb2YgYW55IGV4dGVybmFsIHBhY2thZ2VzLgotICoqRGF0YSBMYXllciAoYGRhdGEvYCkqKjogSW1wbGVtZW50cyB0aGUgcmVwb3NpdG9yaWVzIGRlZmluZWQgaW4gdGhlIGRvbWFpbiBsYXllci4gSGFuZGxlcyBkYXRhIHNvdXJjZXMgKEFQSXMsIExvY2FsIERCcykgYW5kIG1hcHBpbmcgdG8gRGF0YSBNb2RlbHMgdXNpbmcgYGRhcnRfbWFwcGFibGVgLgotICoqUHJlc2VudGF0aW9uIExheWVyIChgcHJlc2VudGF0aW9uL2ApKio6IENvbnRhaW5zIHRoZSBVSSB3aWRnZXRzLCBzY3JlZW5zLCBhbmQgVmlld01vZGVscyAodXNpbmcgUml2ZXJwb2QpIHRoYXQgbWFuYWdlIHRoZSBzdGF0ZSBvZiB0aGUgdmlld3MuCgojIyMgR2VuZXJhdGluZyBGZWF0dXJlcwoKVG8gcXVpY2tseSBzY2FmZm9sZCBhIG5ldyBmZWF0dXJlIHdpdGggdGhpcyBleGFjdCBhcmNoaXRlY3R1cmUsIHJ1bjoKCmBgYHNoCnhkZXZzX2NsaSBjcmVhdGUgZmVhdHVyZSA8ZmVhdHVyZV9uYW1lPgojIE9SCnhkZXZzX2NsaSBjcmVhdGUgZiA8ZmVhdHVyZV9uYW1lPgpgYGAKCkV4YW1wbGU6IGB4ZGV2c19jbGkgY3JlYXRlIGZlYXR1cmUgYXV0aGAgd2lsbCBnZW5lcmF0ZSBgbGliL2ZlYXR1cmVzL2F1dGhgIHdpdGggdGhlIGRvbWFpbiwgZGF0YSwgYW5kIHByZXNlbnRhdGlvbiBmb2xkZXJzIHJlYWR5IHRvIGdvIQoKIyMgR2V0dGluZyBTdGFydGVkIPCfmoAKCjEuIEluc3RhbGwgZGVwZW5kZW5jaWVzOiBgZmx1dHRlciBwdWIgZ2V0YAoyLiBSdW4gY29kZSBnZW5lcmF0aW9uIGZvciBEYXRhIE1vZGVscyBhbmQgUml2ZXJwb2QgUHJvdmlkZXJzOgogICBgYGBzaAogICBkYXJ0IHJ1biBidWlsZF9ydW5uZXIgYnVpbGQgLWQKICAgYGBgCjMuIFJ1biB0aGUgYXBwOiBgZmx1dHRlciBydW5gCkhhcHB5IGNvZGluZyEg8J+agA==", + "type": "text", + }, + ], + "hooks": [], + "name": "base_project", + "description": "Generates the base Flutter project structure.", + "version": "0.1.0+1", + "environment": {"mason": "any"}, + "readme": { + "path": "README.md", + "data": + "IyBiYXNlX3Byb2plY3QKCkdlbmVyYXRlcyB0aGUgYmFzZSBGbHV0dGVyIHByb2plY3Qgc3RydWN0dXJlLg==", + "type": "text", + }, + "changelog": { + "path": "CHANGELOG.md", + "data": "IyBDaGFuZ2Vsb2cKCiMjIDAuMS4wKzEKCi0gSW5pdGlhbCB2ZXJzaW9uLg==", + "type": "text", + }, + "license": { + "path": "LICENSE", + "data": "TUlUIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNg==", + "type": "text", + }, + "vars": { + "project_name": { + "type": "string", + "description": "Project name in snake_case.", + "default": "snow_app", + "prompt": "What is the project name?", + }, + }, +}); diff --git a/lib/src/bundles/feature_module_bundle.dart b/lib/src/bundles/feature_module_bundle.dart new file mode 100644 index 0000000..7158b90 --- /dev/null +++ b/lib/src/bundles/feature_module_bundle.dart @@ -0,0 +1,135 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, implicit_dynamic_list_literal, implicit_dynamic_map_literal, inference_failure_on_collection_literal + +import 'package:mason/mason.dart'; + +final featureModuleBundle = MasonBundle.fromJson({ + "files": [ + { + "path": "lib/features/{{feature_name.snakeCase()}}/data/data.dart", + "data": + "Ly8vIERhdGEgbGF5ZXIgZm9yIHt7ZmVhdHVyZV9uYW1lLnBhc2NhbENhc2UoKX19IGZlYXR1cmUuCi8vLyAKLy8vIEV4cG9ydCBhbGwgZGF0YXNvdXJjZXMsIG1vZGVscywgYW5kIHJlcG9zaXRvcnkgaW1wbGVtZW50YXRpb25zIGhlcmUuCgovLyBleHBvcnQgJ2RhdGFzb3VyY2VzL3t7ZmVhdHVyZV9uYW1lLnNuYWtlQ2FzZSgpfX1fZGF0YXNvdXJjZS5kYXJ0JzsKLy8gZXhwb3J0ICdtb2RlbHMve3tmZWF0dXJlX25hbWUuc25ha2VDYXNlKCl9fV9tb2RlbC5kYXJ0JzsKLy8gZXhwb3J0ICdyZXBvc2l0b3JpZXNfaW1wbC97e2ZlYXR1cmVfbmFtZS5zbmFrZUNhc2UoKX19X3JlcG9zaXRvcnlfaW1wbC5kYXJ0JzsK", + "type": "text", + }, + { + "path": + "lib/features/{{feature_name.snakeCase()}}/data/datasources/.gitkeep", + "data": "", + "type": "text", + }, + { + "path": "lib/features/{{feature_name.snakeCase()}}/data/models/.gitkeep", + "data": "", + "type": "text", + }, + { + "path": + "lib/features/{{feature_name.snakeCase()}}/data/repositories_impl/.gitkeep", + "data": "", + "type": "text", + }, + { + "path": "lib/features/{{feature_name.snakeCase()}}/domain/domain.dart", + "data": + "Ly8vIERvbWFpbiBsYXllciBmb3Ige3tmZWF0dXJlX25hbWUucGFzY2FsQ2FzZSgpfX0gZmVhdHVyZS4KLy8vIAovLy8gRXhwb3J0IGFsbCBlbnRpdGllcywgYWJzdHJhY3QgcmVwb3NpdG9yaWVzLCBhbmQgdXNlIGNhc2VzIGhlcmUuCgpleHBvcnQgJ3VzZWNhc2VzL2dldF97e2ZlYXR1cmVfbmFtZS5zbmFrZUNhc2UoKX19X2RhdGFfdXNlY2FzZS5kYXJ0JzsKCi8vIGV4cG9ydCAnZW50aXRpZXMve3tmZWF0dXJlX25hbWUuc25ha2VDYXNlKCl9fV9lbnRpdHkuZGFydCc7Ci8vIGV4cG9ydCAncmVwb3NpdG9yaWVzL3t7ZmVhdHVyZV9uYW1lLnNuYWtlQ2FzZSgpfX1fcmVwb3NpdG9yeS5kYXJ0JzsK", + "type": "text", + }, + { + "path": + "lib/features/{{feature_name.snakeCase()}}/domain/entities/.gitkeep", + "data": "", + "type": "text", + }, + { + "path": + "lib/features/{{feature_name.snakeCase()}}/domain/repositories/.gitkeep", + "data": "", + "type": "text", + }, + { + "path": + "lib/features/{{feature_name.snakeCase()}}/domain/usecases/get_{{feature_name.snakeCase()}}_data_usecase.dart", + "data": + "Y2xhc3MgR2V0e3tmZWF0dXJlX25hbWUucGFzY2FsQ2FzZSgpfX1EYXRhVXNlQ2FzZSB7CiAgY29uc3QgR2V0e3tmZWF0dXJlX25hbWUucGFzY2FsQ2FzZSgpfX1EYXRhVXNlQ2FzZSgpOwoKICBGdXR1cmU8dm9pZD4gY2FsbCgpIGFzeW5jIHsKICAgIC8vIEltcGxlbWVudCB1c2UgY2FzZSBsb2dpYyBoZXJlCiAgICBhd2FpdCBGdXR1cmUuZGVsYXllZChjb25zdCBEdXJhdGlvbihzZWNvbmRzOiAxKSk7CiAgfQp9Cg==", + "type": "text", + }, + { + "path": + "lib/features/{{feature_name.snakeCase()}}/presentation/providers/providers.dart", + "data": + "ZXhwb3J0ICd7e2ZlYXR1cmVfbmFtZS5zbmFrZUNhc2UoKX19X3Byb3ZpZGVyLmRhcnQnOwo=", + "type": "text", + }, + { + "path": + "lib/features/{{feature_name.snakeCase()}}/presentation/providers/{{feature_name.snakeCase()}}_provider.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOnJpdmVycG9kX2Fubm90YXRpb24vcml2ZXJwb2RfYW5ub3RhdGlvbi5kYXJ0JzsKaW1wb3J0ICcuLi92aWV3bW9kZWxzL3t7ZmVhdHVyZV9uYW1lLnNuYWtlQ2FzZSgpfX1fdmlld21vZGVsLmRhcnQnOwoKcGFydCAne3tmZWF0dXJlX25hbWUuc25ha2VDYXNlKCl9fV9wcm92aWRlci5nLmRhcnQnOwoKQHJpdmVycG9kCmNsYXNzIHt7ZmVhdHVyZV9uYW1lLnBhc2NhbENhc2UoKX19IGV4dGVuZHMgXyR7e2ZlYXR1cmVfbmFtZS5wYXNjYWxDYXNlKCl9fSB7CiAgQG92ZXJyaWRlCiAge3tmZWF0dXJlX25hbWUucGFzY2FsQ2FzZSgpfX1WaWV3TW9kZWwgYnVpbGQoKSB7CiAgICByZXR1cm4gY29uc3Qge3tmZWF0dXJlX25hbWUucGFzY2FsQ2FzZSgpfX1WaWV3TW9kZWwoKTsKICB9CgogIC8vIEFkZCBidXNpbmVzcyBsb2dpYyBtZXRob2RzIGhlcmUKfQo=", + "type": "text", + }, + { + "path": + "lib/features/{{feature_name.snakeCase()}}/presentation/viewmodels/viewmodels.dart", + "data": + "ZXhwb3J0ICd7e2ZlYXR1cmVfbmFtZS5zbmFrZUNhc2UoKX19X3ZpZXdtb2RlbC5kYXJ0JzsK", + "type": "text", + }, + { + "path": + "lib/features/{{feature_name.snakeCase()}}/presentation/viewmodels/{{feature_name.snakeCase()}}_viewmodel.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmRhcnRfbWFwcGFibGUvZGFydF9tYXBwYWJsZS5kYXJ0JzsKCnBhcnQgJ3t7ZmVhdHVyZV9uYW1lLnNuYWtlQ2FzZSgpfX1fdmlld21vZGVsLm1hcHBlci5kYXJ0JzsKCkBNYXBwYWJsZUNsYXNzKCkKY2xhc3Mge3tmZWF0dXJlX25hbWUucGFzY2FsQ2FzZSgpfX1WaWV3TW9kZWwgd2l0aCB7e2ZlYXR1cmVfbmFtZS5wYXNjYWxDYXNlKCl9fVZpZXdNb2RlbE1hcHBhYmxlIHsKICBjb25zdCB7e2ZlYXR1cmVfbmFtZS5wYXNjYWxDYXNlKCl9fVZpZXdNb2RlbCh7CiAgICB0aGlzLmlzTG9hZGluZyA9IGZhbHNlLAogIH0pOwoKICBmaW5hbCBib29sIGlzTG9hZGluZzsKfQo=", + "type": "text", + }, + { + "path": + "lib/features/{{feature_name.snakeCase()}}/presentation/views/{{feature_name.snakeCase()}}_screen.dart", + "data": + "aW1wb3J0ICdwYWNrYWdlOmZsdXR0ZXIvbWF0ZXJpYWwuZGFydCc7CmltcG9ydCAncGFja2FnZTpmbHV0dGVyX3JpdmVycG9kL2ZsdXR0ZXJfcml2ZXJwb2QuZGFydCc7CgppbXBvcnQgJy4uL3Byb3ZpZGVycy97e2ZlYXR1cmVfbmFtZS5zbmFrZUNhc2UoKX19X3Byb3ZpZGVyLmRhcnQnOwoKY2xhc3Mge3tmZWF0dXJlX25hbWUucGFzY2FsQ2FzZSgpfX1TY3JlZW4gZXh0ZW5kcyBDb25zdW1lcldpZGdldCB7CiAgY29uc3Qge3tmZWF0dXJlX25hbWUucGFzY2FsQ2FzZSgpfX1TY3JlZW4oe3N1cGVyLmtleX0pOwoKICBAb3ZlcnJpZGUKICBXaWRnZXQgYnVpbGQoQnVpbGRDb250ZXh0IGNvbnRleHQsIFdpZGdldFJlZiByZWYpIHsKICAgIGZpbmFsIHZpZXdNb2RlbCA9IHJlZi53YXRjaCh7e2ZlYXR1cmVfbmFtZS5jYW1lbENhc2UoKX19UHJvdmlkZXIpOwogICAgCiAgICByZXR1cm4gU2NhZmZvbGQoCiAgICAgIGFwcEJhcjogQXBwQmFyKHRpdGxlOiBjb25zdCBUZXh0KCd7e2ZlYXR1cmVfbmFtZS5wYXNjYWxDYXNlKCl9fScpKSwKICAgICAgYm9keTogQ2VudGVyKAogICAgICAgIGNoaWxkOiB2aWV3TW9kZWwuaXNMb2FkaW5nIAogICAgICAgICAgPyBjb25zdCBDaXJjdWxhclByb2dyZXNzSW5kaWNhdG9yKCkgCiAgICAgICAgICA6IGNvbnN0IFRleHQoJ3t7ZmVhdHVyZV9uYW1lLnBhc2NhbENhc2UoKX19IFNjcmVlbicpLAogICAgICApLAogICAgKTsKICB9Cn0=", + "type": "text", + }, + { + "path": + "lib/features/{{feature_name.snakeCase()}}/presentation/widgets/widgets.dart", + "data": + "Ly8gRXhwb3J0IHdpZGdldHMgdG8gYmUgdXNlZCBpbiB0aGUgZmVhdHVyZQovLyBJdCdzIG5vdCBjb21tb24gdG8gZXhwb3J0IGludGVybmFsIHdpZGdldHMgb3V0c2lkZSBvZiB0aGUgcHJlc2VudGF0aW9uIGxheWVyCi8vIFRoaXMgZmlsZSBpcyB1c2VmdWwgZm9yIHZpZXdzIHRoYXQgbmVlZCB0byBhY2Nlc3MgbXVsdGlwbGUgd2lkZ2V0cyB3aXRob3V0Ci8vIGltcG9ydGluZyB0aGVtIGluZGl2aWR1YWxseQo=", + "type": "text", + }, + ], + "hooks": [], + "name": "feature_module", + "description": "Generates the clean structure for a new feature.", + "version": "0.1.0+1", + "environment": {"mason": "any"}, + "readme": { + "path": "README.md", + "data": + "IyBmZWF0dXJlX21vZHVsZQoKWyFbUG93ZXJlZCBieSBNYXNvbl0oaHR0cHM6Ly9pbWcuc2hpZWxkcy5pby9lbmRwb2ludD91cmw9aHR0cHMlM0ElMkYlMkZ0aW55dXJsLmNvbSUyRm1hc29uLWJhZGdlKV0oaHR0cHM6Ly9naXRodWIuY29tL2ZlbGFuZ2VsL21hc29uKQoKQSBuZXcgYnJpY2sgY3JlYXRlZCB3aXRoIHRoZSBNYXNvbiBDTEkuCgpfR2VuZXJhdGVkIGJ5IFttYXNvbl1bMV0g8J+nsV8KCiMjIEdldHRpbmcgU3RhcnRlZCDwn5qACgpUaGlzIGlzIGEgc3RhcnRpbmcgcG9pbnQgZm9yIGEgbmV3IGJyaWNrLgpBIGZldyByZXNvdXJjZXMgdG8gZ2V0IHlvdSBzdGFydGVkIGlmIHRoaXMgaXMgeW91ciBmaXJzdCBicmljayB0ZW1wbGF0ZToKCi0gW09mZmljaWFsIE1hc29uIERvY3VtZW50YXRpb25dWzJdCi0gW0NvZGUgZ2VuZXJhdGlvbiB3aXRoIE1hc29uIEJsb2ddWzNdCi0gW1ZlcnkgR29vZCBMaXZlc3RyZWFtOiBGZWxpeCBBbmdlbG92IERlbW9zIE1hc29uXVs0XQotIFtGbHV0dGVyIFBhY2thZ2Ugb2YgdGhlIFdlZWs6IE1hc29uXVs1XQotIFtPYnNlcnZhYmxlIEZsdXR0ZXI6IEJ1aWxkaW5nIGEgTWFzb24gYnJpY2tdWzZdCi0gW01lZXQgTWFzb246IEZsdXR0ZXIgVmlraW5ncyAyMDIyXVs3XQoKWzFdOiBodHRwczovL2dpdGh1Yi5jb20vZmVsYW5nZWwvbWFzb24KWzJdOiBodHRwczovL2RvY3MuYnJpY2todWIuZGV2ClszXTogaHR0cHM6Ly92ZXJ5Z29vZC52ZW50dXJlcy9ibG9nL2NvZGUtZ2VuZXJhdGlvbi13aXRoLW1hc29uCls0XTogaHR0cHM6Ly95b3V0dS5iZS9HNFBUakE2dHBUVQpbNV06IGh0dHBzOi8veW91dHUuYmUvcWpBMEpGaVBNblEKWzZdOiBodHRwczovL3lvdXR1LmJlL284QjFFZmNVaXN3Cls3XTogaHR0cHM6Ly95b3V0dS5iZS9MWGhnaUY1SGlRZwo=", + "type": "text", + }, + "changelog": { + "path": "CHANGELOG.md", + "data": "IyAwLjEuMCsxCgotIFRPRE86IERlc2NyaWJlIGluaXRpYWwgcmVsZWFzZS4K", + "type": "text", + }, + "license": { + "path": "LICENSE", + "data": "VE9ETzogQWRkIHlvdXIgbGljZW5zZSBoZXJlLgo=", + "type": "text", + }, + "vars": { + "project_name": { + "type": "string", + "description": "Project name in snake_case.", + "default": "snow_app", + "prompt": "What is the project name?", + }, + "feature_name": { + "type": "string", + "description": "Feature name (ej. budget, auth, household)", + "default": "home", + "prompt": "How is the feature called?", + }, + }, +}); From 84d4378ca592cefd1cf4ec37abfe09a9cdf2ead6 Mon Sep 17 00:00:00 2001 From: AlexitoSnow Date: Sat, 4 Jul 2026 21:32:38 -0500 Subject: [PATCH 3/5] feat(cli): add create feature command and core utilities This commit introduces the 'create feature' command to generate new feature modules based on the feature_module brick. It also adds core utilities for process running and mason service execution, and updates the command runner to register the new command. --- lib/src/command_runner.dart | 2 + lib/src/commands/commands.dart | 1 + lib/src/commands/config_command.dart | 7 +- lib/src/commands/create_command.dart | 381 ++----------------- lib/src/commands/create_feature_command.dart | 224 +++++++++++ lib/src/core/mason_service.dart | 44 +++ lib/src/core/process_runner.dart | 86 +++++ 7 files changed, 390 insertions(+), 355 deletions(-) create mode 100644 lib/src/commands/create_feature_command.dart create mode 100644 lib/src/core/mason_service.dart create mode 100644 lib/src/core/process_runner.dart diff --git a/lib/src/command_runner.dart b/lib/src/command_runner.dart index 563cf17..6af9219 100644 --- a/lib/src/command_runner.dart +++ b/lib/src/command_runner.dart @@ -41,6 +41,8 @@ class XdevsCliCommandRunner extends CompletionCommandRunner { // Add sub commands addCommand(ConfigCommand(logger: _logger)); addCommand(CreateCommand(logger: _logger)); + addCommand(CreateFeatureCommand(logger: _logger, commandName: 'feature')); + addCommand(CreateFeatureCommand(logger: _logger, commandName: 'f')); addCommand(UpdateCommand(logger: _logger, pubUpdater: _pubUpdater)); } diff --git a/lib/src/commands/commands.dart b/lib/src/commands/commands.dart index 2683001..56649c9 100644 --- a/lib/src/commands/commands.dart +++ b/lib/src/commands/commands.dart @@ -1,3 +1,4 @@ export 'config_command.dart'; export 'create_command.dart'; +export 'create_feature_command.dart'; export 'update_command.dart'; diff --git a/lib/src/commands/config_command.dart b/lib/src/commands/config_command.dart index fb9a074..865d425 100644 --- a/lib/src/commands/config_command.dart +++ b/lib/src/commands/config_command.dart @@ -58,9 +58,10 @@ class ConfigCommand extends Command { final config = CliConfig(org: org, platforms: platforms); await _configStore.write(config); - _logger..success('Defaults saved.') - ..info(' org: ${config.org}') - ..info(' platforms: ${config.platforms}'); + _logger + ..success('Defaults saved.') + ..info(' org: ${config.org}') + ..info(' platforms: ${config.platforms}'); return ExitCode.success.code; } } diff --git a/lib/src/commands/create_command.dart b/lib/src/commands/create_command.dart index 1cdfe4f..f9cfb68 100644 --- a/lib/src/commands/create_command.dart +++ b/lib/src/commands/create_command.dart @@ -1,26 +1,25 @@ import 'dart:io'; -import 'dart:isolate'; import 'package:args/args.dart'; import 'package:args/command_runner.dart'; import 'package:mason/mason.dart'; +import 'package:mason_logger/mason_logger.dart'; import 'package:path/path.dart' as p; - import 'package:xdevs_cli/src/config/cli_config_store.dart'; +import 'package:xdevs_cli/src/core/mason_service.dart'; +import 'package:xdevs_cli/src/core/process_runner.dart'; const commonDependencies = [ 'flutter_riverpod', - 'freezed_annotation', + 'dart_mappable', 'go_router', - 'json_annotation', 'riverpod_annotation', ]; const commonDevDependencies = [ 'build_runner', 'flutter_lints', - 'freezed', - 'json_serializable', + 'dart_mappable_builder', 'riverpod_generator', ]; @@ -30,25 +29,16 @@ const commonDevDependencies = [ /// {@endtemplate} class CreateCommand extends Command { /// {@macro create_command} - CreateCommand({required Logger logger, CliConfigStore? configStore}) - : _logger = logger, - _configStore = configStore ?? CliConfigStore() { - final featureParser = ArgParser() - ..addOption( - 'directory', - help: 'Base directory used to locate an existing Flutter project.', - ) - ..addFlag( - 'help', - abbr: 'h', - negatable: false, - help: - 'Show help for the feature subcommand. Feature names can be ' - 'provided as a comma-separated list, e.g. "foo,bar".', - ); + CreateCommand({ + required Logger logger, + CliConfigStore? configStore, + ProcessRunner? processRunner, + MasonService? masonService, + }) : _logger = logger, + _configStore = configStore ?? CliConfigStore(), + _processRunner = processRunner ?? const ProcessRunner(), + _masonService = masonService ?? const MasonService() { argParser - ..addCommand('feature', featureParser) - ..addCommand('f', featureParser) ..addOption( 'directory', help: 'Base directory where the Flutter project will be created.', @@ -72,6 +62,8 @@ class CreateCommand extends Command { final Logger _logger; final CliConfigStore _configStore; + final ProcessRunner _processRunner; + final MasonService _masonService; @override String get description => @@ -85,13 +77,6 @@ class CreateCommand extends Command { @override Future run() async { - final subcommand = argResults?.command?.name; - final featureResults = argResults?.command; - if ((subcommand == 'feature' || subcommand == 'f') && - featureResults != null) { - return _runFeatureCommand(featureResults); - } - final projectName = argResults?.rest.isNotEmpty == true ? argResults!.rest.first : null; @@ -141,20 +126,12 @@ class CreateCommand extends Command { } final createProgress = _logger.progress('Creating Flutter project'); - final createResult = await _runProcess( - 'flutter', - [ - 'create', - '--no-pub', - '--org', - org, - '--platforms', - platforms, - '--description', - description, - normalizedProjectName, - ], + final createResult = await _processRunner.createFlutterProject( workingDirectory: baseDirectory.path, + projectName: normalizedProjectName, + org: org, + platforms: platforms, + description: description, ); if (createResult.exitCode != ExitCode.success.code) { createProgress.fail(); @@ -163,21 +140,11 @@ class CreateCommand extends Command { } createProgress.complete('Created $normalizedProjectName'); - final brickPath = p.join( - await _packageRootPath(), - 'bin', - 'bricks', - 'base_project', - ); - final generator = await MasonGenerator.fromBrick(Brick.path(brickPath)); - final templateProgress = _logger.progress('Applying project template'); try { - final target = DirectoryGeneratorTarget(targetDirectory); - await generator.generate( - target, - vars: {'project_name': normalizedProjectName}, - fileConflictResolution: FileConflictResolution.overwrite, + await _masonService.generateBaseProject( + targetDirectory: targetDirectory, + projectName: normalizedProjectName, ); } on Exception catch (error) { templateProgress.fail(); @@ -189,7 +156,7 @@ class CreateCommand extends Command { final dependenciesProgress = _logger.progress( 'Adding common dependencies', ); - final dependenciesResult = await _addPackages( + final dependenciesResult = await _processRunner.addPackages( workingDirectory: targetDirectory.path, packageNames: commonDependencies, ); @@ -208,7 +175,7 @@ class CreateCommand extends Command { final devDependenciesProgress = _logger.progress( 'Adding common dev dependencies', ); - final devDependenciesResult = await _addPackages( + final devDependenciesResult = await _processRunner.addPackages( workingDirectory: targetDirectory.path, packageNames: commonDevDependencies, devDependency: true, @@ -226,9 +193,7 @@ class CreateCommand extends Command { devDependenciesProgress.complete('Added common dev dependencies'); final buildRunnerProgress = _logger.progress('Running build_runner'); - final buildRunnerResult = await _runProcess( - 'dart', - const ['run', 'build_runner', 'build', '-d'], + final buildRunnerResult = await _processRunner.runBuildRunner( workingDirectory: targetDirectory.path, ); if (buildRunnerResult.exitCode != ExitCode.success.code) { @@ -245,9 +210,9 @@ class CreateCommand extends Command { ..info(' flutter run') ..info('') ..info(' # Create a new feature inside the project:') - ..info(' xdevs_cli create feature ') + ..info(' xdevs_cli feature ') ..info(' # Short alias:') - ..info(' xdevs_cli create f '); + ..info(' xdevs_cli f '); return ExitCode.success.code; } @@ -271,292 +236,4 @@ class CreateCommand extends Command { final stderr = result.stderr.toString().trim(); return stderr.isEmpty ? fallbackMessage : stderr; } - - Future _addPackages({ - required String workingDirectory, - required List packageNames, - bool devDependency = false, - }) { - return _runProcess( - 'flutter', - [ - 'pub', - 'add', - if (devDependency) '--dev', - ...packageNames, - ], - workingDirectory: workingDirectory, - ); - } - - Future _runProcess( - String executable, - List arguments, { - required String workingDirectory, - }) async { - try { - return Process.run( - executable, - arguments, - workingDirectory: workingDirectory, - runInShell: true, - ); - } on ProcessException catch (error) { - return ProcessResult( - 0, - ExitCode.software.code, - '', - error.toString(), - ); - } - } - - Future _packageRootPath() async { - final packageUri = Uri.parse('package:xdevs_cli/xdevs_cli.dart'); - final resolvedPackageUri = await Isolate.resolvePackageUri(packageUri); - if (resolvedPackageUri == null) { - return Directory.current.path; - } - - final packageFile = File.fromUri(resolvedPackageUri); - return p.dirname(p.dirname(packageFile.path)); - } - - Future _runFeatureCommand(ArgResults featureResults) { - return CreateFeatureCommand( - logger: _logger, - commandName: featureResults.name ?? 'feature', - ).runWithResults(featureResults); - } -} - -/// {@template create_feature_command} -/// Creates a feature module inside an existing Flutter project. -/// {@endtemplate} -class CreateFeatureCommand extends Command { - /// {@macro create_feature_command} - CreateFeatureCommand({required Logger logger, required String commandName}) - : _logger = logger, - _commandName = commandName; - - final Logger _logger; - final String _commandName; - - @override - String get description => - 'Create one or more features using the local feature brick ' - '(comma-separated names allowed). Example: xdevs_cli create feature ' - 'foo,bar,baz. Existing features will be skipped and reported.'; - - @override - String get name => _commandName; - - @override - String get invocation => - 'xdevs_cli create $_commandName '; - - @override - String get usage { - final base = super.usage; - final examples = - ''' -Examples: - # Create a single feature - xdevs_cli create $_commandName my_feature - - # Create multiple features (comma-separated). Existing features will be skipped and reported: - xdevs_cli create $_commandName feature_one,feature_two,feature_three -'''; - return '$base\n$examples'; - } - - @override - Future run() async { - final results = argResults; - if (results == null) { - throw StateError('Feature command arguments were not initialized.'); - } - return runWithResults(results); - } - - Future runWithResults(ArgResults featureResults) async { - final raw = featureResults.rest.isNotEmpty - ? featureResults.rest.join(' ') - : null; - if (raw == null || raw.trim().isEmpty) { - _logger - ..err('Missing required argument: .') - ..info(usage); - return ExitCode.usage.code; - } - - final names = raw - .split(',') - .map((s) => s.trim()) - .where((s) => s.isNotEmpty) - .toList(); - if (names.isEmpty) { - _logger - ..err('Missing required argument: .') - ..info(usage); - return ExitCode.usage.code; - } - - final directoryArg = _stringArgFrom(featureResults, 'directory'); - final baseDirectory = directoryArg == null - ? Directory.current - : Directory(p.normalize(directoryArg)); - - if (!baseDirectory.existsSync()) { - _logger.err('Base directory does not exist: ${baseDirectory.path}'); - return ExitCode.usage.code; - } - - final projectRoot = await _resolveFlutterProjectRoot(baseDirectory); - if (projectRoot == null) { - _logger.err( - 'No Flutter project found. Run this command inside a Flutter project ' - 'or inside its lib/ folder, or pass --directory pointing inside the ' - 'project.', - ); - return ExitCode.usage.code; - } - - final projectName = _readProjectName(projectRoot); - if (projectName == null || projectName.isEmpty) { - _logger.err( - 'Could not read the Flutter package name from ' - '${p.join(projectRoot.path, 'pubspec.yaml')}.', - ); - return ExitCode.data.code; - } - - final brickPath = p.join( - await _packageRootPath(), - 'bin', - 'bricks', - 'feature_module', - ); - final generator = await MasonGenerator.fromBrick(Brick.path(brickPath)); - - var createdCount = 0; - final skipped = []; - final invalid = []; - - for (final rawName in names) { - final featureName = rawName.trim(); - if (!_isValidName(featureName)) { - invalid.add(featureName); - _logger.err( - 'Invalid feature name "$featureName". Use lowercase snake_case.', - ); - continue; - } - - final featureDirectory = Directory( - p.join(projectRoot.path, 'lib', 'features', featureName), - ); - if (featureDirectory.existsSync()) { - skipped.add(featureName); - _logger.info( - 'Feature already exists: ${featureDirectory.path} (skipped)', - ); - continue; - } - - final generateProgress = _logger.progress( - 'Generating $featureName feature', - ); - try { - final target = DirectoryGeneratorTarget(projectRoot); - await generator.generate( - target, - vars: { - 'project_name': projectName, - 'feature_name': featureName, - }, - fileConflictResolution: FileConflictResolution.overwrite, - ); - } on Exception catch (error) { - generateProgress.fail(); - _logger.err('Failed to generate feature $featureName: $error'); - continue; - } - generateProgress.complete('Generated $featureName feature'); - _logger.success('Feature "$featureName" created successfully.'); - createdCount++; - } - - if (createdCount > 0) { - _logger.info('Created $createdCount feature(s).'); - } - if (skipped.isNotEmpty) { - _logger.info('Skipped existing features: ${skipped.join(', ')}'); - } - if (invalid.isNotEmpty) { - _logger.info('Invalid feature names skipped: ${invalid.join(', ')}'); - } - - return ExitCode.success.code; - } - - bool _isValidName(String value) { - return RegExp(r'^[a-z][a-z0-9_]*$').hasMatch(value); - } - - String? _stringArgFrom(ArgResults? results, String name) { - final value = results?[name] as String?; - if (value == null || value.trim().isEmpty) { - return null; - } - return value.trim(); - } - - Future _resolveFlutterProjectRoot( - Directory startDirectory, - ) async { - var currentDirectory = startDirectory; - while (true) { - if (_isFlutterProjectRoot(currentDirectory)) { - return currentDirectory; - } - - final parentDirectory = currentDirectory.parent; - if (parentDirectory.path == currentDirectory.path) { - return null; - } - - currentDirectory = parentDirectory; - } - } - - bool _isFlutterProjectRoot(Directory directory) { - final pubspec = File(p.join(directory.path, 'pubspec.yaml')); - final libDirectory = Directory(p.join(directory.path, 'lib')); - return pubspec.existsSync() && libDirectory.existsSync(); - } - - String? _readProjectName(Directory projectRoot) { - final pubspec = File(p.join(projectRoot.path, 'pubspec.yaml')); - if (!pubspec.existsSync()) { - return null; - } - - final match = RegExp( - r'^name:\s*(.+)$', - multiLine: true, - ).firstMatch(pubspec.readAsStringSync()); - return match?.group(1)?.trim(); - } - - Future _packageRootPath() async { - final packageUri = Uri.parse('package:xdevs_cli/xdevs_cli.dart'); - final resolvedPackageUri = await Isolate.resolvePackageUri(packageUri); - if (resolvedPackageUri == null) { - return Directory.current.path; - } - - final packageFile = File.fromUri(resolvedPackageUri); - return p.dirname(p.dirname(packageFile.path)); - } } diff --git a/lib/src/commands/create_feature_command.dart b/lib/src/commands/create_feature_command.dart new file mode 100644 index 0000000..0f3af3b --- /dev/null +++ b/lib/src/commands/create_feature_command.dart @@ -0,0 +1,224 @@ +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:args/command_runner.dart'; +import 'package:mason_logger/mason_logger.dart'; +import 'package:path/path.dart' as p; +import 'package:xdevs_cli/src/core/mason_service.dart'; + +/// {@template create_feature_command} +/// Creates a feature module inside an existing Flutter project. +/// {@endtemplate} +class CreateFeatureCommand extends Command { + /// {@macro create_feature_command} + CreateFeatureCommand({ + required Logger logger, + required String commandName, + MasonService? masonService, + }) : _logger = logger, + _commandName = commandName, + _masonService = masonService ?? const MasonService() { + argParser.addOption( + 'directory', + help: 'Base directory used to locate an existing Flutter project.', + ); + } + + final Logger _logger; + final String _commandName; + final MasonService _masonService; + + @override + String get description => + 'Create one or more features using the local feature brick ' + '(comma-separated names allowed). Example: xdevs_cli feature ' + 'foo,bar,baz. Existing features will be skipped and reported.'; + + @override + String get name => _commandName; + + @override + String get invocation => + 'xdevs_cli $_commandName '; + + @override + String get usage { + final base = super.usage; + final examples = ''' +Examples: + # Create a single feature + xdevs_cli $_commandName my_feature + + # Create multiple features (comma-separated): + xdevs_cli $_commandName feature_one,feature_two,feature_three +'''; + return '$base\n$examples'; + } + + @override + Future run() async { + final results = argResults; + if (results == null) { + throw StateError('Feature command arguments were not initialized.'); + } + return runWithResults(results); + } + + Future runWithResults(ArgResults featureResults) async { + final raw = featureResults.rest.isNotEmpty + ? featureResults.rest.join(' ') + : null; + if (raw == null || raw.trim().isEmpty) { + _logger + ..err('Missing required argument: .') + ..info(usage); + return ExitCode.usage.code; + } + + final names = raw + .split(',') + .map((s) => s.trim()) + .where((s) => s.isNotEmpty) + .toList(); + if (names.isEmpty) { + _logger + ..err('Missing required argument: .') + ..info(usage); + return ExitCode.usage.code; + } + + final directoryArg = _stringArgFrom(featureResults, 'directory'); + final baseDirectory = directoryArg == null + ? Directory.current + : Directory(p.normalize(directoryArg)); + + if (!baseDirectory.existsSync()) { + _logger.err('Base directory does not exist: ${baseDirectory.path}'); + return ExitCode.usage.code; + } + + final projectRoot = await _resolveFlutterProjectRoot(baseDirectory); + if (projectRoot == null) { + _logger.err( + 'No Flutter project found. Run this command inside a Flutter project ' + 'or inside its lib/ folder, or pass --directory pointing inside the ' + 'project.', + ); + return ExitCode.usage.code; + } + + final projectName = _readProjectName(projectRoot); + if (projectName == null || projectName.isEmpty) { + _logger.err( + 'Could not read the Flutter package name from ' + '${p.join(projectRoot.path, 'pubspec.yaml')}.', + ); + return ExitCode.data.code; + } + + var createdCount = 0; + final skipped = []; + final invalid = []; + + for (final rawName in names) { + final featureName = rawName.trim(); + if (!_isValidName(featureName)) { + invalid.add(featureName); + _logger.err( + 'Invalid feature name "$featureName". Use lowercase snake_case.', + ); + continue; + } + + final featureDirectory = Directory( + p.join(projectRoot.path, 'lib', 'features', featureName), + ); + if (featureDirectory.existsSync()) { + skipped.add(featureName); + _logger.info( + 'Feature already exists: ${featureDirectory.path} (skipped)', + ); + continue; + } + + final generateProgress = _logger.progress( + 'Generating $featureName feature', + ); + try { + await _masonService.generateFeatureModule( + targetDirectory: projectRoot, + projectName: projectName, + featureName: featureName, + ); + } on Exception catch (error) { + generateProgress.fail(); + _logger.err('Failed to generate feature $featureName: $error'); + continue; + } + generateProgress.complete('Generated $featureName feature'); + _logger.success('Feature "$featureName" created successfully.'); + createdCount++; + } + + if (createdCount > 0) { + _logger.info('Created $createdCount feature(s).'); + } + if (skipped.isNotEmpty) { + _logger.info('Skipped existing features: ${skipped.join(', ')}'); + } + if (invalid.isNotEmpty) { + _logger.info('Invalid feature names skipped: ${invalid.join(', ')}'); + } + + return ExitCode.success.code; + } + + bool _isValidName(String value) { + return RegExp(r'^[a-z][a-z0-9_]*$').hasMatch(value); + } + + String? _stringArgFrom(ArgResults? results, String name) { + final value = results?[name] as String?; + if (value == null || value.trim().isEmpty) { + return null; + } + return value.trim(); + } + + Future _resolveFlutterProjectRoot( + Directory startDirectory, + ) async { + var currentDirectory = startDirectory; + while (true) { + if (_isFlutterProjectRoot(currentDirectory)) { + return currentDirectory; + } + + final parentDirectory = currentDirectory.parent; + if (parentDirectory.path == currentDirectory.path) { + return null; + } + + currentDirectory = parentDirectory; + } + } + + bool _isFlutterProjectRoot(Directory directory) { + final pubspec = File(p.join(directory.path, 'pubspec.yaml')); + final libDirectory = Directory(p.join(directory.path, 'lib')); + return pubspec.existsSync() && libDirectory.existsSync(); + } + + String? _readProjectName(Directory projectRoot) { + final pubspec = File(p.join(projectRoot.path, 'pubspec.yaml')); + if (!pubspec.existsSync()) { + return null; + } + + final match = RegExp( + r'^name:\s*(.+)$', + multiLine: true, + ).firstMatch(pubspec.readAsStringSync()); + return match?.group(1)?.trim(); + } +} diff --git a/lib/src/core/mason_service.dart b/lib/src/core/mason_service.dart new file mode 100644 index 0000000..8d9eb03 --- /dev/null +++ b/lib/src/core/mason_service.dart @@ -0,0 +1,44 @@ +import 'dart:io'; + +import 'package:mason/mason.dart'; +import 'package:xdevs_cli/src/bundles/base_project_bundle.dart'; +import 'package:xdevs_cli/src/bundles/feature_module_bundle.dart'; + +/// A service to handle template generation using Mason bundles. +class MasonService { + const MasonService(); + + /// Generates the base Flutter project template. + Future generateBaseProject({ + required Directory targetDirectory, + required String projectName, + }) async { + final generator = await MasonGenerator.fromBundle(baseProjectBundle); + final target = DirectoryGeneratorTarget(targetDirectory); + + await generator.generate( + target, + vars: {'project_name': projectName}, + fileConflictResolution: FileConflictResolution.overwrite, + ); + } + + /// Generates a feature module template. + Future generateFeatureModule({ + required Directory targetDirectory, + required String projectName, + required String featureName, + }) async { + final generator = await MasonGenerator.fromBundle(featureModuleBundle); + final target = DirectoryGeneratorTarget(targetDirectory); + + await generator.generate( + target, + vars: { + 'project_name': projectName, + 'feature_name': featureName, + }, + fileConflictResolution: FileConflictResolution.overwrite, + ); + } +} diff --git a/lib/src/core/process_runner.dart b/lib/src/core/process_runner.dart new file mode 100644 index 0000000..b07ef8c --- /dev/null +++ b/lib/src/core/process_runner.dart @@ -0,0 +1,86 @@ +import 'dart:io'; + +import 'package:mason_logger/mason_logger.dart'; + +/// A utility class for executing shell commands, +/// specifically focused on Flutter and Dart CLI operations. +class ProcessRunner { + const ProcessRunner(); + + /// Runs a process and returns the [ProcessResult]. + Future run( + String executable, + List arguments, { + required String workingDirectory, + }) async { + try { + return await Process.run( + executable, + arguments, + workingDirectory: workingDirectory, + runInShell: true, + ); + } on ProcessException catch (error) { + return ProcessResult( + 0, + ExitCode.software.code, + '', + error.toString(), + ); + } + } + + /// Adds packages to a Flutter project using `flutter pub add`. + Future addPackages({ + required String workingDirectory, + required List packageNames, + bool devDependency = false, + }) { + return run( + 'flutter', + [ + 'pub', + 'add', + if (devDependency) '--dev', + ...packageNames, + ], + workingDirectory: workingDirectory, + ); + } + + /// Creates a new Flutter project using `flutter create`. + Future createFlutterProject({ + required String workingDirectory, + required String projectName, + required String org, + required String platforms, + required String description, + }) { + return run( + 'flutter', + [ + 'create', + '--no-pub', + '--org', + org, + '--platforms', + platforms, + '--description', + description, + projectName, + ], + workingDirectory: workingDirectory, + ); + } + + /// Runs `dart run build_runner build -d`. + Future runBuildRunner({ + required String workingDirectory, + }) { + return run( + 'dart', + const ['run', 'build_runner', 'build', '-d'], + workingDirectory: workingDirectory, + ); + } +} From 90e74c93350fe50438ea9b9dc71691141728b73b Mon Sep 17 00:00:00 2001 From: AlexitoSnow Date: Sat, 4 Jul 2026 21:32:43 -0500 Subject: [PATCH 4/5] test(cli): add unit tests for commands and core utilities This commit introduces comprehensive tests for the command runner, config command, create command, and create feature command, aiming to improve code reliability and test coverage. --- coverage_badge.svg | 20 -- dart_test.yaml | 3 - test/src/commands/config_command_test.dart | 121 +++++++ test/src/commands/create_command_test.dart | 321 ++++++++++++++++++ .../commands/create_feature_command_test.dart | 263 ++++++++++++++ 5 files changed, 705 insertions(+), 23 deletions(-) delete mode 100644 coverage_badge.svg delete mode 100644 dart_test.yaml create mode 100644 test/src/commands/config_command_test.dart create mode 100644 test/src/commands/create_command_test.dart create mode 100644 test/src/commands/create_feature_command_test.dart diff --git a/coverage_badge.svg b/coverage_badge.svg deleted file mode 100644 index 88bfadf..0000000 --- a/coverage_badge.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - coverage - coverage - 100% - 100% - - \ No newline at end of file diff --git a/dart_test.yaml b/dart_test.yaml deleted file mode 100644 index 34bbdd0..0000000 --- a/dart_test.yaml +++ /dev/null @@ -1,3 +0,0 @@ -tags: - version-verify: - skip: "Should only be run during pull request. Verifies if version file is updated." diff --git a/test/src/commands/config_command_test.dart b/test/src/commands/config_command_test.dart new file mode 100644 index 0000000..973c765 --- /dev/null +++ b/test/src/commands/config_command_test.dart @@ -0,0 +1,121 @@ +import 'package:args/command_runner.dart'; +import 'package:mason_logger/mason_logger.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; +import 'package:xdevs_cli/src/commands/config_command.dart'; +import 'package:xdevs_cli/src/config/cli_config.dart'; +import 'package:xdevs_cli/src/config/cli_config_store.dart'; + +class _MockLogger extends Mock implements Logger {} + +class _MockCliConfigStore extends Mock implements CliConfigStore {} + +void main() { + group('ConfigCommand', () { + late Logger logger; + late CliConfigStore configStore; + late CommandRunner commandRunner; + + setUp(() { + logger = _MockLogger(); + configStore = _MockCliConfigStore(); + + when(() => configStore.read()).thenAnswer((_) async => null); + when(() => configStore.write(any())).thenAnswer((_) async {}); + + when( + () => logger.prompt(any(), defaultValue: any(named: 'defaultValue')), + ).thenAnswer((invocation) { + return invocation.namedArguments[#defaultValue] as String; + }); + + commandRunner = CommandRunner('xdevs_cli', 'Test runner') + ..addCommand(ConfigCommand(logger: logger, configStore: configStore)); + }); + + setUpAll(() { + registerFallbackValue(const CliConfig(org: 'org', platforms: 'p')); + }); + + test('can be instantiated without explicit store', () { + final command = ConfigCommand(logger: logger); + expect(command, isNotNull); + }); + + test('prompts for defaults when no arguments provided', () async { + when( + () => logger.prompt('Default org', defaultValue: 'com.example'), + ).thenReturn('com.test'); + when( + () => logger.prompt( + 'Default platforms', + defaultValue: 'android,ios,linux,macos,web,windows', + ), + ).thenReturn('ios'); + + final result = await commandRunner.run(['config']); + + expect(result, equals(ExitCode.success.code)); + verify( + () => configStore.write( + any( + that: isA() + .having((c) => c.org, 'org', 'com.test') + .having((c) => c.platforms, 'platforms', 'ios'), + ), + ), + ).called(1); + verify(() => logger.success('Defaults saved.')).called(1); + }); + + test('uses provided arguments and skips prompts', () async { + final result = await commandRunner.run([ + 'config', + '--org', + 'com.new', + '--platforms', + 'android', + ]); + + expect(result, equals(ExitCode.success.code)); + verify( + () => configStore.write( + any( + that: isA() + .having((c) => c.org, 'org', 'com.new') + .having((c) => c.platforms, 'platforms', 'android'), + ), + ), + ).called(1); + verifyNever( + () => logger.prompt(any(), defaultValue: any(named: 'defaultValue')), + ); + }); + + test('reads existing config for prompt defaults', () async { + when(() => configStore.read()).thenAnswer( + (_) async => const CliConfig(org: 'com.old', platforms: 'web'), + ); + + when( + () => logger.prompt('Default org', defaultValue: 'com.old'), + ).thenReturn('com.old'); + when( + () => logger.prompt('Default platforms', defaultValue: 'web'), + ).thenReturn('web'); + + final result = await commandRunner.run(['config']); + + expect(result, equals(ExitCode.success.code)); + verify( + () => configStore.write( + any( + that: isA() + .having((c) => c.org, 'org', 'com.old') + .having((c) => c.platforms, 'platforms', 'web'), + ), + ), + ).called(1); + }); + }); +} diff --git a/test/src/commands/create_command_test.dart b/test/src/commands/create_command_test.dart new file mode 100644 index 0000000..27d47a8 --- /dev/null +++ b/test/src/commands/create_command_test.dart @@ -0,0 +1,321 @@ +import 'dart:io'; + +import 'package:args/command_runner.dart'; +import 'package:mason_logger/mason_logger.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; +import 'package:xdevs_cli/src/commands/create_command.dart'; +import 'package:xdevs_cli/src/config/cli_config.dart'; +import 'package:xdevs_cli/src/config/cli_config_store.dart'; +import 'package:xdevs_cli/src/core/mason_service.dart'; +import 'package:xdevs_cli/src/core/process_runner.dart'; + +class _MockLogger extends Mock implements Logger {} + +class _MockProgress extends Mock implements Progress {} + +class _MockCliConfigStore extends Mock implements CliConfigStore {} + +class _MockProcessRunner extends Mock implements ProcessRunner {} + +class _MockMasonService extends Mock implements MasonService {} + +void main() { + group('CreateCommand', () { + late Logger logger; + late Progress progress; + late CliConfigStore configStore; + late ProcessRunner processRunner; + late MasonService masonService; + late CommandRunner commandRunner; + + setUp(() { + logger = _MockLogger(); + progress = _MockProgress(); + configStore = _MockCliConfigStore(); + processRunner = _MockProcessRunner(); + masonService = _MockMasonService(); + + when(() => logger.progress(any())).thenReturn(progress); + + when(() => configStore.read()).thenAnswer( + (_) async => + const CliConfig(org: 'com.example', platforms: 'android,ios'), + ); + + when( + () => processRunner.createFlutterProject( + workingDirectory: any(named: 'workingDirectory'), + projectName: any(named: 'projectName'), + org: any(named: 'org'), + platforms: any(named: 'platforms'), + description: any(named: 'description'), + ), + ).thenAnswer((_) async => ProcessResult(0, 0, '', '')); + + when( + () => masonService.generateBaseProject( + targetDirectory: any(named: 'targetDirectory'), + projectName: any(named: 'projectName'), + ), + ).thenAnswer((_) async {}); + + when( + () => processRunner.addPackages( + workingDirectory: any(named: 'workingDirectory'), + packageNames: any(named: 'packageNames'), + devDependency: any(named: 'devDependency'), + ), + ).thenAnswer((_) async => ProcessResult(0, 0, '', '')); + + when( + () => processRunner.runBuildRunner( + workingDirectory: any(named: 'workingDirectory'), + ), + ).thenAnswer((_) async => ProcessResult(0, 0, '', '')); + + final command = CreateCommand( + logger: logger, + configStore: configStore, + processRunner: processRunner, + masonService: masonService, + ); + + commandRunner = CommandRunner('xdevs_cli', 'Test runner') + ..addCommand(command); + }); + + setUpAll(() { + registerFallbackValue(Directory.current); + }); + + test('can be instantiated without explicit injected deps', () { + final command = CreateCommand(logger: logger); + expect(command, isNotNull); + }); + + test('returns usage code when project name is missing', () async { + final result = await commandRunner.run(['create']); + expect(result, equals(ExitCode.usage.code)); + verify( + () => logger.err('Missing required argument: .'), + ).called(1); + }); + + test('returns usage code when project name is invalid', () async { + final result = await commandRunner.run(['create', 'Invalid-Name']); + expect(result, equals(ExitCode.usage.code)); + verify( + () => logger.err(any(that: contains('Invalid project name'))), + ).called(1); + }); + + test('returns usage code when base directory does not exist', () async { + final result = await commandRunner.run([ + 'create', + 'valid_name', + '--directory', + 'non/existent/path/that/hopefully/does/not/exist', + ]); + expect(result, equals(ExitCode.usage.code)); + verify( + () => logger.err(any(that: contains('Base directory does not exist'))), + ).called(1); + }); + + test('returns usage code when target directory already exists', () async { + final tempDir = Directory.systemTemp.createTempSync(); + final targetDir = Directory(p.join(tempDir.path, 'valid_name')) + ..createSync(); + + final result = await commandRunner.run([ + 'create', + 'valid_name', + '--directory', + tempDir.path, + ]); + expect(result, equals(ExitCode.usage.code)); + verify( + () => + logger.err(any(that: contains('Target directory already exists'))), + ).called(1); + + targetDir.deleteSync(); + tempDir.deleteSync(); + }); + + test('creates project successfully', () async { + final tempDir = Directory.systemTemp.createTempSync(); + + final result = await commandRunner.run([ + 'create', + 'valid_name', + '--directory', + tempDir.path, + ]); + + expect(result, equals(ExitCode.success.code)); + verify( + () => processRunner.createFlutterProject( + workingDirectory: any(named: 'workingDirectory'), + projectName: 'valid_name', + org: 'com.example', + platforms: 'android,ios', + description: 'A new Flutter project made with xdevs-cli.', + ), + ).called(1); + verify( + () => masonService.generateBaseProject( + targetDirectory: any(named: 'targetDirectory'), + projectName: 'valid_name', + ), + ).called(1); + verify( + () => processRunner.addPackages( + workingDirectory: any(named: 'workingDirectory'), + packageNames: any(named: 'packageNames'), + ), + ).called(1); + verify( + () => processRunner.addPackages( + workingDirectory: any(named: 'workingDirectory'), + packageNames: any(named: 'packageNames'), + devDependency: true, + ), + ).called(1); + verify( + () => processRunner.runBuildRunner( + workingDirectory: any(named: 'workingDirectory'), + ), + ).called(1); + verify( + () => logger.success(any(that: contains('created successfully'))), + ).called(1); + + tempDir.deleteSync(recursive: true); + }); + + test('handles create process failure', () async { + final tempDir = Directory.systemTemp.createTempSync(); + + when( + () => processRunner.createFlutterProject( + workingDirectory: any(named: 'workingDirectory'), + projectName: any(named: 'projectName'), + org: any(named: 'org'), + platforms: any(named: 'platforms'), + description: any(named: 'description'), + ), + ).thenAnswer( + (_) async => ProcessResult(0, 1, '', 'flutter create error'), + ); + + final result = await commandRunner.run([ + 'create', + 'valid_name', + '--directory', + tempDir.path, + ]); + + expect(result, equals(1)); + verify(() => logger.err('flutter create error')).called(1); + + tempDir.deleteSync(recursive: true); + }); + + test('handles mason generate failure', () async { + final tempDir = Directory.systemTemp.createTempSync(); + + when( + () => masonService.generateBaseProject( + targetDirectory: any(named: 'targetDirectory'), + projectName: any(named: 'projectName'), + ), + ).thenThrow(Exception('mason error')); + + final result = await commandRunner.run([ + 'create', + 'valid_name', + '--directory', + tempDir.path, + ]); + + expect(result, equals(ExitCode.software.code)); + verify( + () => logger.err(any(that: contains('Failed to apply template'))), + ).called(1); + + tempDir.deleteSync(recursive: true); + }); + + test('handles add packages failure', () async { + final tempDir = Directory.systemTemp.createTempSync(); + + when( + () => processRunner.addPackages( + workingDirectory: any(named: 'workingDirectory'), + packageNames: any(named: 'packageNames'), + ), + ).thenAnswer((_) async => ProcessResult(0, 1, '', 'pub add error')); + + final result = await commandRunner.run([ + 'create', + 'valid_name', + '--directory', + tempDir.path, + ]); + + expect(result, equals(1)); + verify(() => logger.err('pub add error')).called(1); + + tempDir.deleteSync(recursive: true); + }); + + test('handles dev packages failure', () async { + final tempDir = Directory.systemTemp.createTempSync(); + + when( + () => processRunner.addPackages( + workingDirectory: any(named: 'workingDirectory'), + packageNames: any(named: 'packageNames'), + devDependency: true, + ), + ).thenAnswer((_) async => ProcessResult(0, 1, '', 'pub add dev error')); + + final result = await commandRunner.run([ + 'create', + 'valid_name', + '--directory', + tempDir.path, + ]); + + expect(result, equals(1)); + verify(() => logger.err('pub add dev error')).called(1); + + tempDir.deleteSync(recursive: true); + }); + + test('handles build_runner failure', () async { + final tempDir = Directory.systemTemp.createTempSync(); + + when( + () => processRunner.runBuildRunner( + workingDirectory: any(named: 'workingDirectory'), + ), + ).thenAnswer((_) async => ProcessResult(0, 1, '', 'build runner error')); + + final result = await commandRunner.run([ + 'create', + 'valid_name', + '--directory', + tempDir.path, + ]); + + expect(result, equals(1)); + verify(() => logger.err('build runner error')).called(1); + + tempDir.deleteSync(recursive: true); + }); + }); +} diff --git a/test/src/commands/create_feature_command_test.dart b/test/src/commands/create_feature_command_test.dart new file mode 100644 index 0000000..212c786 --- /dev/null +++ b/test/src/commands/create_feature_command_test.dart @@ -0,0 +1,263 @@ +import 'dart:io'; + +import 'package:args/command_runner.dart'; +import 'package:mason_logger/mason_logger.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; +import 'package:xdevs_cli/src/commands/create_feature_command.dart'; +import 'package:xdevs_cli/src/core/mason_service.dart'; + +class _MockLogger extends Mock implements Logger {} + +class _MockProgress extends Mock implements Progress {} + +class _MockMasonService extends Mock implements MasonService {} + +void main() { + group('CreateFeatureCommand', () { + late Logger logger; + late Progress progress; + late MasonService masonService; + late CommandRunner commandRunner; + late Directory tempDir; + + setUp(() { + logger = _MockLogger(); + progress = _MockProgress(); + masonService = _MockMasonService(); + + when(() => logger.progress(any())).thenReturn(progress); + + when( + () => masonService.generateFeatureModule( + targetDirectory: any(named: 'targetDirectory'), + projectName: any(named: 'projectName'), + featureName: any(named: 'featureName'), + ), + ).thenAnswer((_) async {}); + + final command = CreateFeatureCommand( + logger: logger, + commandName: 'feature', + masonService: masonService, + ); + + commandRunner = CommandRunner('xdevs_cli', 'Test runner') + ..addCommand(command); + + tempDir = Directory.systemTemp.createTempSync(); + }); + + tearDown(() { + tempDir.deleteSync(recursive: true); + }); + + setUpAll(() { + registerFallbackValue(Directory.current); + }); + + void createValidProjectRoot(Directory dir) { + File( + p.join(dir.path, 'pubspec.yaml'), + ).writeAsStringSync('name: valid_project\n'); + Directory(p.join(dir.path, 'lib')).createSync(); + } + + test('can be instantiated without explicit injected deps', () { + final command = CreateFeatureCommand( + logger: logger, + commandName: 'feature', + ); + expect(command, isNotNull); + }); + + test('returns usage code when feature name is missing', () async { + final result = await commandRunner.run(['feature']); + expect(result, equals(ExitCode.usage.code)); + verify( + () => logger.err('Missing required argument: .'), + ).called(1); + }); + + test('returns usage code when base directory does not exist', () async { + final result = await commandRunner.run([ + 'feature', + 'valid_name', + '--directory', + 'non/existent/path', + ]); + expect(result, equals(ExitCode.usage.code)); + verify( + () => logger.err(any(that: contains('Base directory does not exist'))), + ).called(1); + }); + + test('returns usage code when no flutter project found', () async { + final result = await commandRunner.run([ + 'feature', + 'valid_name', + '--directory', + tempDir.path, + ]); + expect(result, equals(ExitCode.usage.code)); + verify( + () => logger.err(any(that: contains('No Flutter project found'))), + ).called(1); + }); + + test('returns data error when pubspec is missing project name', () async { + File(p.join(tempDir.path, 'pubspec.yaml')).writeAsStringSync(''); + Directory(p.join(tempDir.path, 'lib')).createSync(); + + final result = await commandRunner.run([ + 'feature', + 'valid_name', + '--directory', + tempDir.path, + ]); + expect(result, equals(ExitCode.data.code)); + verify( + () => logger.err( + any(that: contains('Could not read the Flutter package name')), + ), + ).called(1); + }); + + test('creates single feature successfully', () async { + createValidProjectRoot(tempDir); + + final result = await commandRunner.run([ + 'feature', + 'valid_name', + '--directory', + tempDir.path, + ]); + + expect(result, equals(ExitCode.success.code)); + verify( + () => masonService.generateFeatureModule( + targetDirectory: any(named: 'targetDirectory'), + projectName: 'valid_project', + featureName: 'valid_name', + ), + ).called(1); + verify( + () => logger.success(any(that: contains('created successfully'))), + ).called(1); + }); + + test('creates multiple features successfully', () async { + createValidProjectRoot(tempDir); + + final result = await commandRunner.run([ + 'feature', + 'feature_one,feature_two', + '--directory', + tempDir.path, + ]); + + expect(result, equals(ExitCode.success.code)); + verify( + () => masonService.generateFeatureModule( + targetDirectory: any(named: 'targetDirectory'), + projectName: 'valid_project', + featureName: 'feature_one', + ), + ).called(1); + verify( + () => masonService.generateFeatureModule( + targetDirectory: any(named: 'targetDirectory'), + projectName: 'valid_project', + featureName: 'feature_two', + ), + ).called(1); + verify(() => logger.info('Created 2 feature(s).')).called(1); + }); + + test('handles invalid feature names', () async { + createValidProjectRoot(tempDir); + + final result = await commandRunner.run([ + 'feature', + 'Invalid-Name,valid_name', + '--directory', + tempDir.path, + ]); + + expect(result, equals(ExitCode.success.code)); + verify( + () => masonService.generateFeatureModule( + targetDirectory: any(named: 'targetDirectory'), + projectName: 'valid_project', + featureName: 'valid_name', + ), + ).called(1); + verify( + () => logger.err( + any(that: contains('Invalid feature name "Invalid-Name"')), + ), + ).called(1); + verify( + () => logger.info( + any(that: contains('Invalid feature names skipped: Invalid-Name')), + ), + ).called(1); + }); + + test('handles existing features', () async { + createValidProjectRoot(tempDir); + Directory( + p.join(tempDir.path, 'lib', 'features', 'existing_feature'), + ).createSync(recursive: true); + + final result = await commandRunner.run([ + 'feature', + 'existing_feature', + '--directory', + tempDir.path, + ]); + + expect(result, equals(ExitCode.success.code)); + verifyNever( + () => masonService.generateFeatureModule( + targetDirectory: any(named: 'targetDirectory'), + projectName: any(named: 'projectName'), + featureName: any(named: 'featureName'), + ), + ); + verify( + () => logger.info( + any(that: contains('Skipped existing features: existing_feature')), + ), + ).called(1); + }); + + test('handles mason generate failure', () async { + createValidProjectRoot(tempDir); + + when( + () => masonService.generateFeatureModule( + targetDirectory: any(named: 'targetDirectory'), + projectName: any(named: 'projectName'), + featureName: any(named: 'featureName'), + ), + ).thenThrow(Exception('mason error')); + + final result = await commandRunner.run([ + 'feature', + 'valid_name', + '--directory', + tempDir.path, + ]); + + // The command continues even on individual feature failure and returns success + expect(result, equals(ExitCode.success.code)); + verify( + () => logger.err( + any(that: contains('Failed to generate feature valid_name')), + ), + ).called(1); + }); + }); +} From 31c2efe3dba3c0d850584e8447eb1e3364a2dee4 Mon Sep 17 00:00:00 2001 From: AlexitoSnow Date: Sat, 4 Jul 2026 21:32:48 -0500 Subject: [PATCH 5/5] chore(pub): prepare package for pub.dev release This commit prepares the package for publishing on pub.dev by updating the pubspec.yaml metadata, creating a comprehensive README, adding an example, configuring pubignore, and addressing analysis warnings. --- .gitignore | 1 - .pubignore | 3 + CHANGELOG.md | 5 + LICENSE | 2 +- README.md | 75 ++--- analysis_options.yaml | 5 + example/README.md | 26 ++ example/example.dart | 24 ++ pubspec.lock | 621 ++++++++++++++++++++++++++++++++++++++++++ pubspec.yaml | 4 +- 10 files changed, 718 insertions(+), 48 deletions(-) create mode 100644 .pubignore create mode 100644 CHANGELOG.md create mode 100644 example/README.md create mode 100644 example/example.dart create mode 100644 pubspec.lock diff --git a/.gitignore b/.gitignore index 9f6ee8a..207caa2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ .dart_tool/ .packages build/ -pubspec.lock # Files generated during tests .test_coverage.dart diff --git a/.pubignore b/.pubignore new file mode 100644 index 0000000..6031b0d --- /dev/null +++ b/.pubignore @@ -0,0 +1,3 @@ +bricks/ +coverage/ +.github/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b66cbec --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## 0.0.1 + +- Initial version. diff --git a/LICENSE b/LICENSE index 2c1c5f1..157a4e7 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 +Copyright (c) 2026 XitoDev Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index cf9b390..65f32c2 100644 --- a/README.md +++ b/README.md @@ -1,84 +1,69 @@ ## xdevs_cli -![coverage][coverage_badge] -[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link] [![License: MIT][license_badge]][license_link] -Generated by the [Very Good CLI][very_good_cli_link] ๐Ÿค– - -A Very Good Project created by Very Good CLI.. +A Flutter Command-Line Interface designed by [XitoDev](https://github.com/XitoDev) to quickly bootstrap scalable Flutter projects and feature modules based on Clean Architecture principles. --- +## What's included? ๐Ÿ“ฆ + +When you generate a project with `xdevs_cli`, it comes pre-configured with: + +- **State Management**: [Riverpod](https://pub.dev/packages/flutter_riverpod) +- **Routing**: [GoRouter](https://pub.dev/packages/go_router) +- **Code Generation & Data Models**: [Dart Mappable](https://pub.dev/packages/dart_mappable) +- **Linter**: Strictly follows Very Good Analysis rules. + ## Getting Started ๐Ÿš€ -If the CLI application is available on [pub](https://pub.dev), activate globally via: +Activate globally via [pub.dev](https://pub.dev): ```sh dart pub global activate xdevs_cli ``` -Or locally via: +## Usage -```sh -dart pub global activate --source=path -``` +### Create a new project -## Usage +This command creates a complete Flutter app configured with Riverpod, GoRouter, and Dart Mappable. ```sh -# Create a project xdevs_cli create my_app - -# Create a single feature inside an existing project -xdevs_cli create feature my_feature - -# Create multiple features (comma-separated) -xdevs_cli create feature foo,bar,baz ``` -## Next steps +### Create new features -After creating a project with `xdevs_cli create `, run the following commands: +Navigate to your project (or any folder inside it) and create one or multiple features at once. This scaffolds the Domain, Data, and Presentation layers for you. ```sh # Enter the project -cd +cd my_app -# Run the app -flutter run +# Create a single feature +xdevs_cli feature settings -# Create one or more features inside the project (comma-separated names allowed) -# Example: -xdevs_cli create feature my_feature,another_feature +# Create multiple features at once (comma-separated) +xdevs_cli feature auth,home,profile ``` -## Running Tests with coverage ๐Ÿงช - -To run all unit tests use the following command: - +**Tip:** You can use the short alias `f` instead of `feature`: ```sh -$ dart pub global activate coverage 1.15.0 -$ dart test --coverage=coverage -$ dart pub global run coverage:format_coverage --lcov --in=coverage --out=coverage/lcov.info +xdevs_cli f orders ``` -To view the generated coverage report you can use [lcov](https://github.com/linux-test-project/lcov) -. +## Running Tests ๐Ÿงช -```sh -# Generate Coverage Report -$ genhtml coverage/lcov.info -o coverage/ +To run all unit tests with coverage: -# Open Coverage Report -$ open coverage/index.html +```sh +dart pub global activate coverage +dart test --coverage=coverage +dart pub global run coverage:format_coverage --lcov --in=coverage --out=coverage/lcov.info ``` --- -[coverage_badge]: coverage_badge.svg [license_badge]: https://img.shields.io/badge/license-MIT-blue.svg -[license_link]: https://opensource.org/licenses/MIT -[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg -[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis -[very_good_cli_link]: https://github.com/VeryGoodOpenSource/very_good_cli \ No newline at end of file +[license_link]: https://opensource.org/licenses/MIT \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml index ce42cd4..af44aa7 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,4 +1,9 @@ include: package:very_good_analysis/analysis_options.yaml + +analyzer: + exclude: + - "bricks/**" + linter: rules: public_member_api_docs: false diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..7961dbe --- /dev/null +++ b/example/README.md @@ -0,0 +1,26 @@ +# xdevs_cli Example + +## Installation + +```sh +dart pub global activate xdevs_cli +``` + +## Usage + +### 1. Create a new project +```sh +xdevs_cli create my_app +cd my_app +``` + +### 2. Create a new feature module +Scaffold a complete feature with MVVM, Clean Architecture, and Riverpod Generator. +```sh +xdevs_cli feature settings +``` + +### 3. Create multiple features at once +```sh +xdevs_cli feature auth,home,profile +``` diff --git a/example/example.dart b/example/example.dart new file mode 100644 index 0000000..b9923aa --- /dev/null +++ b/example/example.dart @@ -0,0 +1,24 @@ +// This package is a CLI tool, so you usually run it from the terminal. +// However, if you wanted to trigger it programmatically via Dart, +// you could use `Process.run`: + +import 'dart:io'; + +void main() async { + print('Running xdevs_cli create my_new_app...'); + + final result = await Process.run( + 'xdevs_cli', + ['create', 'my_new_app'], + runInShell: true, + ); + + print('Exit code: ${result.exitCode}'); + if (result.exitCode == 0) { + print('Project created successfully!'); + print(result.stdout); + } else { + print('Error creating project:'); + print(result.stderr); + } +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..018971f --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,621 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: "1b0e6a07425a3e460666e88bf1c949ccc7bb0116ad562ce94a1eca60fe820725" + url: "https://pub.dev" + source: hosted + version: "103.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "61c04d0c1bfed555c681ea079519933f071a5a026578ff73c4ff0df2d3462e5e" + url: "https://pub.dev" + source: hosted + version: "13.3.0" + archive: + dependency: transitive + description: + name: archive + sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff + url: "https://pub.dev" + source: hosted + version: "4.0.9" + args: + dependency: "direct main" + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + build: + dependency: transitive + description: + name: build + sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10 + url: "https://pub.dev" + source: hosted + version: "4.0.6" + build_config: + dependency: transitive + description: + name: build_config + sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 + url: "https://pub.dev" + source: hosted + version: "4.1.1" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" + url: "https://pub.dev" + source: hosted + version: "2.15.0" + build_verify: + dependency: "direct dev" + description: + name: build_verify + sha256: "3b17b59b6d66f9d3e6014996f089902d56cec5760e051c353cc387b9da577652" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + build_version: + dependency: "direct dev" + description: + name: build_version + sha256: "1063066ec338c18f0629d01077c9315f92fae3e7e0e06d0dc10e8aa3145d44f5" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" + url: "https://pub.dev" + source: hosted + version: "8.12.6" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.dev" + source: hosted + version: "2.0.4" + cli_completion: + dependency: "direct main" + description: + name: cli_completion + sha256: "72e8ccc4545f24efa7bbdf3bff7257dc9d62b072dee77513cc54295575bc9220" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + coverage: + dependency: "direct dev" + description: + name: coverage + sha256: "956a3de0725ca232ad353565a8290d3357592bf4250f6f298a185e2d949c5d3d" + url: "https://pub.dev" + source: hosted + version: "1.15.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "59d53ef8eaed9d288ed9767618e2b31c4fa0383a127db59d5eb2e737a7638a60" + url: "https://pub.dev" + source: hosted + version: "3.1.9" + equatable: + dependency: transitive + description: + name: equatable + sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" + url: "https://pub.dev" + source: hosted + version: "2.0.8" + ffi: + dependency: transitive + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + graphs: + dependency: transitive + description: + name: graphs + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + url: "https://pub.dev" + source: hosted + version: "4.12.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + mason: + dependency: "direct main" + description: + name: mason + sha256: "515b28eedc3e106bbbfb95f0ff63471003396978d3413daeba0a64bcac01367b" + url: "https://pub.dev" + source: hosted + version: "0.1.2" + mason_logger: + dependency: "direct main" + description: + name: mason_logger + sha256: "1d46102c6f299c0df7fe986dd3dd3271d57c2ec7c00ae590660b7c3018810048" + url: "https://pub.dev" + source: hosted + version: "0.3.5" + matcher: + dependency: transitive + description: + name: matcher + sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd" + url: "https://pub.dev" + source: hosted + version: "0.12.20" + meta: + dependency: transitive + description: + name: meta + sha256: c82594181e3312f3d0695fc95aaaf7758d75b8d4ae2bbecf223b9fd5109a059d + url: "https://pub.dev" + source: hosted + version: "1.18.3" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + mocktail: + dependency: "direct dev" + description: + name: mocktail + sha256: "5e1bf53cc7baa8062a33b84424deb61513858ea05c601b8509e683815b5914aa" + url: "https://pub.dev" + source: hosted + version: "1.0.5" + mustache_template: + dependency: transitive + description: + name: mustache_template + sha256: c689b4d59176f8c7a2860aab765d205916aa5b06cfafff7613bfcc42fa996143 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: "direct main" + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + posix: + dependency: transitive + description: + name: posix + sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" + url: "https://pub.dev" + source: hosted + version: "6.5.0" + process: + dependency: transitive + description: + name: process + sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744 + url: "https://pub.dev" + source: hosted + version: "5.0.5" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + pub_updater: + dependency: "direct main" + description: + name: pub_updater + sha256: "739a0161d73a6974c0675b864fb0cf5147305f7b077b7f03a58fa7a9ab3e7e7d" + url: "https://pub.dev" + source: hosted + version: "0.5.0" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" + url: "https://pub.dev" + source: hosted + version: "0.10.13" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test: + dependency: "direct dev" + description: + name: test + sha256: "0d5ba5602ec3baa28c8ce365e1efc5575969c765f45c554a3e167dc7945b9c30" + url: "https://pub.dev" + source: hosted + version: "1.31.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "475610b2aa23c19687cce2961e44b0cc57cafe220f67c2b80201231b2a07fbe7" + url: "https://pub.dev" + source: hosted + version: "0.7.13" + test_core: + dependency: transitive + description: + name: test_core + sha256: a39c204a4fc7a7ccb04a2b985e359fda3cc37e45e0b8ac61c3fb1a05aa832132 + url: "https://pub.dev" + source: hosted + version: "0.6.19" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + very_good_analysis: + dependency: "direct dev" + description: + name: very_good_analysis + sha256: "481af67ab5877af20325251dc215a4ebac7666a1c8cf09198ffd457bc612b33d" + url: "https://pub.dev" + source: hosted + version: "10.3.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.dev" + source: hosted + version: "15.2.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + win32: + dependency: transitive + description: + name: win32 + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.dev" + source: hosted + version: "5.15.0" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.12.0 <4.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 41edda9..af97f02 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,8 +1,9 @@ name: xdevs_cli description: A Flutter Command-Line Interface for Flutter projects setup created by XitoDev. version: 0.0.1 -publish_to: none +homepage: https://github.com/XitoDev/xdevs_cli repository: https://github.com/XitoDev/xdevs_cli +issue_tracker: https://github.com/XitoDev/xdevs_cli/issues topics: - cli - flutter @@ -26,6 +27,7 @@ dev_dependencies: build_runner: ^2.15.0 build_verify: ^3.1.1 build_version: ^2.1.3 + coverage: ^1.15.1 mocktail: ^1.0.5 test: ^1.31.1 very_good_analysis: ^10.2.0