├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.yaml │ ├── build.yaml │ ├── chore.yaml │ ├── ci.yaml │ ├── config.yml │ ├── documentation.yaml │ ├── feature_request.yaml │ ├── performance.yaml │ ├── refactor.yaml │ ├── revert.yaml │ ├── style.yaml │ └── test.yaml ├── PULL_REQUEST_TEMPLATE.md ├── cspell.json ├── dependabot.yaml ├── labels.yml └── workflows │ ├── bump_templates.yaml │ ├── ci.yaml │ ├── e2e.yaml │ ├── pub_publish.yaml │ ├── site.yaml │ ├── spdx_license.yaml │ ├── spdx_license_bot.yaml │ ├── sync_labels.yaml │ ├── test_optimizer.yaml │ └── very_good_cli.yaml ├── .gitignore ├── .pubignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── bin └── very_good.dart ├── bricks └── test_optimizer │ ├── CONTRIBUTING.md │ ├── README.md │ ├── __brick__ │ └── test │ │ └── .test_optimizer.dart │ ├── brick.yaml │ └── hooks │ ├── analysis_options.yaml │ ├── lib │ ├── dart_identifier_generator.dart │ └── pre_gen.dart │ ├── pre_gen.dart │ ├── pubspec.yaml │ └── test │ ├── dart_identifier_generator_test.dart │ └── pre_gen_test.dart ├── coverage_badge.svg ├── dart_test.yaml ├── doc └── assets │ ├── very_good_create.gif │ ├── very_good_create.png │ └── vgv_logo.png ├── e2e ├── .gitignore ├── analysis_options.yaml ├── helpers │ ├── command_helper.dart │ ├── copy_directory.dart │ ├── expect_successful_process_result.dart │ └── helpers.dart ├── pubspec.yaml └── test │ └── commands │ ├── create │ ├── dart_cli │ │ └── dart_cli_test.dart │ ├── dart_package │ │ └── dart_pkg_test.dart │ ├── docs_site │ │ └── docs_site_test.dart │ ├── flame_game │ │ └── flame_game_test.dart │ ├── flutter_app │ │ └── core_test.dart │ ├── flutter_package │ │ └── flutter_pkg_test.dart │ └── flutter_plugin │ │ └── flutter_plugin_test.dart │ ├── packages │ └── check │ │ └── licenses │ │ ├── licenses_allowed_test.dart │ │ └── licenses_forbidden_test.dart │ └── test │ ├── async_main │ ├── async_main_test.dart │ └── fixture │ │ ├── pubspec.yaml │ │ └── test │ │ └── async_main_test.dart │ ├── compilation_error │ ├── compilation_error_test.dart │ └── fixture │ │ ├── analysis_options.yaml │ │ ├── lib │ │ └── compilation_error.dart │ │ ├── pubspec.yaml │ │ └── test │ │ └── src │ │ └── my_package_test.dart │ ├── no_project │ ├── fixture │ │ └── pubspec.yaml │ └── no_project_test.dart │ └── spaced_golden_file_name │ ├── fixture │ ├── pubspec.yaml │ └── test │ │ ├── sized box.png │ │ └── spaced_golden_file_name_test.dart │ └── spaced_golden_file_name_test.dart ├── example └── README.md ├── lib ├── src │ ├── cli │ │ ├── cli.dart │ │ ├── dart_cli.dart │ │ ├── flutter_cli.dart │ │ └── git_cli.dart │ ├── command_runner.dart │ ├── commands │ │ ├── commands.dart │ │ ├── create │ │ │ ├── commands │ │ │ │ ├── commands.dart │ │ │ │ ├── create_subcommand.dart │ │ │ │ ├── dart_cli.dart │ │ │ │ ├── dart_package.dart │ │ │ │ ├── docs_site.dart │ │ │ │ ├── flame_game.dart │ │ │ │ ├── flutter_app.dart │ │ │ │ ├── flutter_package.dart │ │ │ │ └── flutter_plugin.dart │ │ │ ├── create.dart │ │ │ └── templates │ │ │ │ ├── post_generate_actions.dart │ │ │ │ ├── template.dart │ │ │ │ ├── templates.dart │ │ │ │ ├── very_good_core │ │ │ │ ├── very_good_core.dart │ │ │ │ ├── very_good_core_bundle.dart │ │ │ │ └── very_good_core_template.dart │ │ │ │ ├── very_good_dart_cli │ │ │ │ ├── very_good_dart_cli.dart │ │ │ │ ├── very_good_dart_cli_bundle.dart │ │ │ │ └── very_good_dart_cli_template.dart │ │ │ │ ├── very_good_dart_package │ │ │ │ ├── very_good_dart_package.dart │ │ │ │ ├── very_good_dart_package_bundle.dart │ │ │ │ └── very_good_dart_package_template.dart │ │ │ │ ├── very_good_docs_site │ │ │ │ ├── very_good_docs_site.dart │ │ │ │ ├── very_good_docs_site_bundle.dart │ │ │ │ └── very_good_docs_site_template.dart │ │ │ │ ├── very_good_flame_game │ │ │ │ ├── very_good_flame_game.dart │ │ │ │ ├── very_good_flame_game_bundle.dart │ │ │ │ └── very_good_flame_game_template.dart │ │ │ │ ├── very_good_flutter_package │ │ │ │ ├── very_good_flutter_package.dart │ │ │ │ ├── very_good_flutter_package_bundle.dart │ │ │ │ └── very_good_flutter_package_template.dart │ │ │ │ ├── very_good_flutter_plugin │ │ │ │ ├── very_good_flutter_plugin.dart │ │ │ │ ├── very_good_flutter_plugin_bundle.dart │ │ │ │ └── very_good_flutter_plugin_template.dart │ │ │ │ └── very_good_wear_app │ │ │ │ ├── very_good_wear_app.dart │ │ │ │ ├── very_good_wear_app_bundle.dart │ │ │ │ └── very_good_wear_app_template.dart │ │ ├── packages │ │ │ ├── commands │ │ │ │ ├── check │ │ │ │ │ ├── check.dart │ │ │ │ │ └── commands │ │ │ │ │ │ ├── commands.dart │ │ │ │ │ │ └── licenses.dart │ │ │ │ ├── commands.dart │ │ │ │ └── get.dart │ │ │ └── packages.dart │ │ ├── test │ │ │ ├── templates │ │ │ │ └── test_optimizer_bundle.dart │ │ │ └── test.dart │ │ └── update.dart │ ├── logger_extension.dart │ ├── pub_license │ │ └── spdx_license.gen.dart │ ├── pubspec_lock │ │ └── pubspec_lock.dart │ └── version.dart └── very_good_cli.dart ├── mason.yaml ├── pubspec.yaml ├── site ├── .github │ ├── ISSUE_TEMPLATE │ │ ├── bug_report.md │ │ ├── build.md │ │ ├── chore.md │ │ ├── ci.md │ │ ├── config.yml │ │ ├── documentation.md │ │ ├── feature_request.md │ │ ├── performance.md │ │ ├── refactor.md │ │ ├── revert.md │ │ ├── style.md │ │ └── test.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dependabot.yml │ └── workflows │ │ └── main.yaml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── README.md ├── babel.config.js ├── docs │ ├── commands │ │ ├── _category_.json │ │ ├── check_licenses.md │ │ ├── get_pkgs.md │ │ └── test.md │ ├── overview.md │ ├── resources │ │ ├── _category_.json │ │ ├── learn_more.md │ │ ├── syntax_changes_in_0_10_0.md │ │ └── tutorials.md │ └── templates │ │ ├── _category_.json │ │ ├── core.md │ │ ├── dart_cli.md │ │ ├── dart_pkg.md │ │ ├── docs_site.md │ │ ├── federated_plugin.md │ │ ├── flame_game.md │ │ ├── flutter_pkg.md │ │ └── wear.md ├── docusaurus.config.js ├── eslint.config.js ├── package-lock.json ├── package.json ├── sidebars.js ├── src │ ├── css │ │ └── custom.css │ └── pages │ │ ├── index.module.css │ │ └── index.tsx ├── static │ ├── .nojekyll │ ├── CNAME │ └── img │ │ ├── cli_icon.svg │ │ ├── core_devices.png │ │ ├── dart_cli_hero.png │ │ ├── docs_dark.png │ │ ├── docs_light.png │ │ ├── docs_overview_dark.png │ │ ├── docs_overview_light.png │ │ ├── favicon.ico │ │ ├── home_hero.svg │ │ ├── icon_best.svg │ │ ├── icon_commands.svg │ │ ├── icon_templates.svg │ │ ├── logo.svg │ │ ├── logo_dark.svg │ │ ├── meta │ │ └── open-graph.png │ │ ├── vgv_logo_black.svg │ │ ├── vgv_logo_fill.svg │ │ └── watch.jpeg └── tsconfig.json ├── test ├── ensure_build_test.dart ├── fixtures │ ├── fixtures.dart │ ├── golden_test.png │ ├── lcov_fixtures.dart │ └── test_runner_fixtures.dart ├── helpers │ ├── command_helper.dart │ ├── helpers.dart │ └── test_multi_template_commands.dart └── src │ ├── cli │ ├── dart_cli_test.dart │ ├── flutter_cli_test.dart │ ├── git_cli_test.dart │ └── process_overrides_test.dart │ ├── command_runner_test.dart │ ├── commands │ ├── create │ │ ├── commands │ │ │ ├── dart_cli_test.dart │ │ │ ├── dart_package_test.dart │ │ │ ├── docs_site_test.dart │ │ │ ├── flame_game_test.dart │ │ │ ├── flutter_app_test.dart │ │ │ ├── flutter_package_test.dart │ │ │ └── flutter_plugin_test.dart │ │ ├── create_subcommand_test.dart │ │ └── create_test.dart │ ├── packages │ │ ├── commands │ │ │ ├── check │ │ │ │ ├── check_test.dart │ │ │ │ └── commands │ │ │ │ │ └── licenses_test.dart │ │ │ └── get_test.dart │ │ └── packages_test.dart │ ├── test │ │ └── test_test.dart │ └── update_test.dart │ ├── logger_extension_test.dart │ └── pubspec_lock │ └── pubspec_lock_test.dart └── tool ├── generate_bundles.sh ├── generate_test_optimizer_bundle.sh ├── release_ready.sh └── spdx_license ├── CONTRIBUTING.md ├── README.md ├── __brick__ └── spdx_license.gen.dart ├── analysis_options.yaml ├── brick.yaml ├── hooks ├── analysis_options.yaml ├── dart_test.yaml ├── post_gen.dart ├── pre_gen.dart ├── pubspec.yaml └── test │ ├── post_gen_test.dart │ └── pre_gen_test.dart ├── pubspec.yaml └── test ├── spdx_license.gen.dart └── spdx_license_test.dart /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Every request must be reviewed and accepted by: 2 | 3 | * @VeryGoodOpenSource/codeowners 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Create a report to help us improve 3 | title: "fix: " 4 | labels: [bug] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: A clear and concise description of what the bug is. 11 | placeholder: "Describe the bug." 12 | validations: 13 | required: true 14 | - type: textarea 15 | id: setps-to-reproduce 16 | attributes: 17 | label: Steps To Reproduce 18 | description: A set of instructions, step by step, explaining how to reproduce the bug. 19 | placeholder: | 20 | 1. Go to '...' 21 | 2. Click on '....' 22 | 3. Scroll down to '....' 23 | 4. See error 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: expected-behavior 28 | attributes: 29 | label: Expected Behavior 30 | description: A clear and concise description of what you expected to happen. 31 | placeholder: "Describe what you expected to happen." 32 | validations: 33 | required: true 34 | - type: textarea 35 | id: additional-context 36 | attributes: 37 | label: Additional Context 38 | description: Add any other context, including links/screenshots/video recordings/etc about the problem here. 39 | placeholder: "Provide context here." 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build System 2 | description: Changes that affect the build system or external dependencies 3 | title: "build: " 4 | labels: [build] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: Describe what changes need to be done to the build system and why 11 | placeholder: "Describe the build system change." 12 | validations: 13 | required: true 14 | - type: textarea 15 | id: requirements 16 | attributes: 17 | label: Requirements 18 | description: The list of requirements that need to be met in order to consider the ticket to be completed. Please be as explicit as possible. 19 | value: | 20 | - [ ] All CI/CD checks are passing. 21 | - [ ] There is no drop in the test coverage percentage. 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: additional-context 26 | attributes: 27 | label: Additional Context 28 | description: Add any other context, including links/screenshots/video recordings/etc about the problem here. 29 | placeholder: "Provide context here." 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/chore.yaml: -------------------------------------------------------------------------------- 1 | name: Chore 2 | description: Other changes that don't modify source or test files 3 | title: "chore: " 4 | labels: [chore] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: Clearly describe what change is needed and why. If this changes code then please use another issue type. 11 | placeholder: "Provide a description of the chore." 12 | validations: 13 | required: true 14 | - type: textarea 15 | id: requirements 16 | attributes: 17 | label: Requirements 18 | description: The list of requirements that need to be met in order to consider the ticket to be completed. Please be as explicit as possible. 19 | value: | 20 | - [ ] No functional changes to the code. 21 | - [ ] All CI/CD checks are passing. 22 | - [ ] There is no drop in the test coverage percentage. 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: additional-context 27 | attributes: 28 | label: Additional Context 29 | description: Add any other context, including links/screenshots/video recordings/etc about the problem here. 30 | placeholder: "Provide context here." 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | description: Changes to the CI configuration files and scripts 3 | title: "ci: " 4 | labels: [ci] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: Describe what changes need to be done to the CI/CD system and why. 11 | placeholder: "Provide a description of the changes that need to be done to the CI/CD system." 12 | validations: 13 | required: true 14 | - type: textarea 15 | id: requirements 16 | attributes: 17 | label: Requirements 18 | description: The list of requirements that need to be met in order to consider the ticket to be completed. Please be as explicit as possible. 19 | value: | 20 | - [ ] All CI/CD checks are passing. 21 | - [ ] There is no drop in the test coverage percentage. 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: additional-context 26 | attributes: 27 | label: Additional Context 28 | description: Add any other context, including links/screenshots/video recordings/etc about the problem here. 29 | placeholder: "Provide context here." 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.yaml: -------------------------------------------------------------------------------- 1 | name: Documentation 2 | description: Improve the documentation so all collaborators have a common understanding 3 | title: "docs: " 4 | labels: [documentation] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: Clearly describe what documentation you are looking to add or improve. 11 | placeholder: "Provide a description of the documentation changes." 12 | validations: 13 | required: true 14 | - type: textarea 15 | id: requirements 16 | attributes: 17 | label: Requirements 18 | description: The list of requirements that need to be met in order to consider the ticket to be completed. Please be as explicit as possible. 19 | value: | 20 | - [ ] No functional changes to the code. 21 | - [ ] All CI/CD checks are passing. 22 | - [ ] There is no drop in the test coverage percentage. 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: additional-context 27 | attributes: 28 | label: Additional Context 29 | description: Add any other context, including links/screenshots/video recordings/etc about the problem here. 30 | placeholder: "Provide context here." 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yaml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: A new feature to be added to the project 3 | title: "feat: " 4 | labels: [feature] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: Clearly describe what you are looking to add. The more business/user context the better. 11 | placeholder: "Provide a description of the feature." 12 | validations: 13 | required: true 14 | - type: textarea 15 | id: requirements 16 | attributes: 17 | label: Requirements 18 | description: The list of requirements that need to be met in order to consider the ticket to be completed. Please be as explicit as possible. 19 | value: | 20 | - [ ] All CI/CD checks are passing. 21 | - [ ] There is no drop in the test coverage percentage. 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: additional-context 26 | attributes: 27 | label: Additional Context 28 | description: Add any other context, including links/screenshots/video recordings/etc about the problem here. 29 | placeholder: "Provide context here." 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/performance.yaml: -------------------------------------------------------------------------------- 1 | name: Performance Update 2 | description: A code change that improves performance 3 | title: "perf: " 4 | labels: [performance] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: Clearly describe what code needs to be changed and what the performance impact is going to be. Bonus point's if you can tie this directly to user experience. 11 | placeholder: " Provide a description of the performance update." 12 | validations: 13 | required: true 14 | - type: textarea 15 | id: requirements 16 | attributes: 17 | label: Requirements 18 | description: The list of requirements that need to be met in order to consider the ticket to be completed. Please be as explicit as possible. 19 | value: | 20 | - [ ] All CI/CD checks are passing. 21 | - [ ] There is no drop in the test coverage percentage. 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: additional-context 26 | attributes: 27 | label: Additional Context 28 | description: Add any other context, including links/screenshots/video recordings/etc about the problem here. 29 | placeholder: "Provide context here." 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/refactor.yaml: -------------------------------------------------------------------------------- 1 | name: Refactor 2 | description: A code change that neither fixes a bug nor adds a feature 3 | title: "refactor: " 4 | labels: [refactor] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: Clearly describe what needs to be refactored and why. Please provide links to related issues (bugs or upcoming features) in order to help prioritize. 11 | placeholder: "Provide a description of the refactor." 12 | validations: 13 | required: true 14 | - type: textarea 15 | id: requirements 16 | attributes: 17 | label: Requirements 18 | description: The list of requirements that need to be met in order to consider the ticket to be completed. Please be as explicit as possible. 19 | value: | 20 | - [ ] All CI/CD checks are passing. 21 | - [ ] There is no drop in the test coverage percentage. 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: additional-context 26 | attributes: 27 | label: Additional Context 28 | description: Add any other context, including links/screenshots/video recordings/etc about the problem here. 29 | placeholder: "Provide context here." 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/revert.yaml: -------------------------------------------------------------------------------- 1 | name: Revert 2 | description: Revert a previous commit 3 | title: "revert: " 4 | labels: [revert] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: Provide a link to a PR/Commit that you are looking to revert and why. 11 | placeholder: "Provide a description of and link to the commit that needs to be reverted." 12 | validations: 13 | required: true 14 | - type: textarea 15 | id: requirements 16 | attributes: 17 | label: Requirements 18 | description: The list of requirements that need to be met in order to consider the ticket to be completed. Please be as explicit as possible. 19 | value: | 20 | - [ ] Change has been reverted. 21 | - [ ] No change in unit/widget test coverage has happened. 22 | - [ ] A new ticket is created for any follow on work that needs to happen. 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: additional-context 27 | attributes: 28 | label: Additional Context 29 | description: Add any other context, including links/screenshots/video recordings/etc about the problem here. 30 | placeholder: "Provide context here." 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/style.yaml: -------------------------------------------------------------------------------- 1 | name: Style 2 | description: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) 3 | title: "style: " 4 | labels: [style] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: Clearly describe what you are looking to change and why. 11 | placeholder: "Provide a description of the style changes." 12 | validations: 13 | required: true 14 | - type: textarea 15 | id: requirements 16 | attributes: 17 | label: Requirements 18 | description: The list of requirements that need to be met in order to consider the ticket to be completed. Please be as explicit as possible. 19 | value: | 20 | - [ ] All CI/CD checks are passing. 21 | - [ ] There is no drop in the unit or widget test coverage percentage. 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: additional-context 26 | attributes: 27 | label: Additional Context 28 | description: Add any other context, including links/screenshots/video recordings/etc about the problem here. 29 | placeholder: "Provide context here." 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | description: Adding missing tests or correcting existing tests 3 | title: "test: " 4 | labels: [test] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: List out the tests that need to be added or changed. Please also include any information as to why this was not covered in the past. 11 | placeholder: "Provide a description of the tests that need to be added or changed." 12 | validations: 13 | required: true 14 | - type: textarea 15 | id: requirements 16 | attributes: 17 | label: Requirements 18 | description: The list of requirements that need to be met in order to consider the ticket to be completed. Please be as explicit as possible. 19 | value: | 20 | - [ ] All CI/CD checks are passing. 21 | - [ ] There is no drop in the test coverage percentage. 22 | validations: 23 | required: true 24 | - type: textarea 25 | id: additional-context 26 | attributes: 27 | label: Additional Context 28 | description: Add any other context, including links/screenshots/video recordings/etc about the problem here. 29 | placeholder: "Provide context here." 30 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | ## Status 8 | 9 | **READY/IN DEVELOPMENT/HOLD** 10 | 11 | ## Description 12 | 13 | 14 | 15 | ## Type of Change 16 | 17 | 18 | 19 | - [ ] ✨ New feature (non-breaking change which adds functionality) 20 | - [ ] 🛠️ Bug fix (non-breaking change which fixes an issue) 21 | - [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change) 22 | - [ ] 🧹 Code refactor 23 | - [ ] ✅ Build configuration change 24 | - [ ] 📝 Documentation 25 | - [ ] 🗑️ Chore 26 | -------------------------------------------------------------------------------- /.github/cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2", 3 | "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", 4 | "dictionaries": ["vgv_allowed", "vgv_forbidden"], 5 | "dictionaryDefinitions": [ 6 | { 7 | "name": "vgv_allowed", 8 | "path": "https://raw.githubusercontent.com/verygoodopensource/very_good_dictionaries/main/allowed.txt", 9 | "description": "Allowed VGV Spellings" 10 | }, 11 | { 12 | "name": "vgv_forbidden", 13 | "path": "https://raw.githubusercontent.com/verygoodopensource/very_good_dictionaries/main/forbidden.txt", 14 | "description": "Forbidden VGV Spellings" 15 | } 16 | ], 17 | "useGitignore": true, 18 | "words": [ 19 | "codeowners", 20 | "contador", 21 | "goldens", 22 | "localizable", 23 | "mipmap", 24 | "mostrado", 25 | "página", 26 | "pregen", 27 | "srealmoreno", 28 | "texto", 29 | "verygoodcore", 30 | "xcassets", 31 | "gradlew", 32 | "fluttium", 33 | "clsx", 34 | "mockingjay" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | - package-ecosystem: "npm" 8 | directory: "/site" 9 | schedule: 10 | interval: "weekly" 11 | - package-ecosystem: "pub" 12 | directory: "/bricks/test_optimizer/hooks" 13 | schedule: 14 | interval: "daily" 15 | - package-ecosystem: "pub" 16 | directory: "/tool/spdx_license/hooks" 17 | schedule: 18 | interval: "daily" 19 | - package-ecosystem: "pub" 20 | directory: "/" 21 | schedule: 22 | interval: "daily" 23 | - package-ecosystem: "pub" 24 | directory: "/e2e" 25 | schedule: 26 | interval: "daily" 27 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | - name: "platform: windows" 2 | color: "78EFFF" 3 | description: Building on or for Windows specifically 4 | aliases: [windows] 5 | -------------------------------------------------------------------------------- /.github/workflows/bump_templates.yaml: -------------------------------------------------------------------------------- 1 | name: update_templates 2 | 3 | on: 4 | schedule: 5 | # weekly on mondays at 8 am utc 6 | - cron: '0 8 * * 1' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - uses: dart-lang/setup-dart@v1 16 | 17 | - name: Install mason 18 | run: dart pub global activate mason_cli 19 | 20 | - name: Bump templates 21 | run: tool/generate_bundles.sh 22 | 23 | - name: Config Git User 24 | run: | 25 | git config user.name VGV Bot 26 | git config user.email vgvbot@users.noreply.github.com 27 | 28 | - name: Create Pull Request 29 | uses: peter-evans/create-pull-request@v7.0.6 30 | with: 31 | base: main 32 | branch: feat/bump-template-bundles 33 | commit-message: "feat: bump template bundles" 34 | title: "feat: bump template bundles" 35 | body: Please squash and merge me! 36 | labels: bot 37 | author: VGV Bot 38 | assignees: vgvbot 39 | committer: VGV Bot 40 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/semantic_pull_request.yml@v1 11 | 12 | spell-check: 13 | uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/spell_check.yml@v1 14 | with: 15 | includes: "**/*.md" 16 | modified_files_only: false 17 | 18 | pana: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: noop 22 | run: echo 'noop' 23 | 24 | e2e: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: noop 28 | run: echo 'noop' 29 | -------------------------------------------------------------------------------- /.github/workflows/e2e.yaml: -------------------------------------------------------------------------------- 1 | name: e2e 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - ".github/workflows/e2e.yaml" 7 | - "**" 8 | - "lib/**" 9 | - "test/**" 10 | - "pubspec.yaml" 11 | push: 12 | branches: 13 | - main 14 | paths: 15 | - ".github/workflows/e2e.yaml" 16 | - "**" 17 | - "lib/**" 18 | - "test/**" 19 | - "pubspec.yaml" 20 | 21 | jobs: 22 | e2e: 23 | runs-on: ubuntu-latest 24 | 25 | strategy: 26 | matrix: 27 | flutter-version: 28 | # The version of Flutter to use should use the minimum Dart SDK version supported by the package, 29 | # refer to https://docs.flutter.dev/development/tools/sdk/releases. 30 | - "3.24.0" 31 | - "3.x" 32 | test: 33 | # E2E tests for the test command 34 | - test/commands/test/async_main/async_main_test.dart 35 | - test/commands/test/compilation_error/compilation_error_test.dart 36 | - test/commands/test/no_project/no_project_test.dart 37 | - test/commands/test/spaced_golden_file_name/spaced_golden_file_name_test.dart 38 | 39 | # E2E tests for the create command 40 | - test/commands/create/flutter_app/core_test.dart 41 | - test/commands/create/dart_cli/dart_cli_test.dart 42 | - test/commands/create/dart_package/dart_pkg_test.dart 43 | - test/commands/create/docs_site/docs_site_test.dart 44 | # FIXME(alestiago): Re-enable once the following issue has been solved: 45 | # https://github.com/VeryGoodOpenSource/very_good_flame_game/issues/132 46 | # - test/commands/create/flame_game/flame_game_test.dart 47 | - test/commands/create/flutter_package/flutter_pkg_test.dart 48 | - test/commands/create/flutter_plugin/flutter_plugin_test.dart 49 | 50 | # E2E tests for the `packages check licenses` command 51 | - test/commands/packages/check/licenses/licenses_allowed_test.dart 52 | - test/commands/packages/check/licenses/licenses_forbidden_test.dart 53 | 54 | steps: 55 | - name: 📚 Git Checkout 56 | uses: actions/checkout@v4 57 | 58 | - name: 🐦 Setup Flutter 59 | uses: subosito/flutter-action@v2 60 | with: 61 | flutter-version: ${{ matrix.flutter-version }} 62 | 63 | - name: Install LCOV 64 | run: sudo apt-get install -y lcov 65 | 66 | - name: 📦 Install Dependencies (root) 67 | run: dart pub get 68 | 69 | - name: 📦 Install Dependencies (e2e) 70 | working-directory: e2e 71 | run: dart pub get 72 | 73 | - name: 🧪 Run Tests (e2e) 74 | working-directory: e2e 75 | run: dart test ${{ matrix.test }} --run-skipped 76 | -------------------------------------------------------------------------------- /.github/workflows/pub_publish.yaml: -------------------------------------------------------------------------------- 1 | name: pub_publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v[0-9]+.[0-9]+.[0-9]+*" # tag pattern on pub.dev: 'v{{version}' 7 | workflow_dispatch: 8 | 9 | jobs: 10 | publish: 11 | permissions: 12 | id-token: write # Required for authentication using OIDC 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: 📚 Git Checkout 16 | uses: actions/checkout@v4 17 | - name: 🎯 Setup Dart 18 | uses: dart-lang/setup-dart@v1 19 | - name: 📦 Install Dependencies 20 | run: dart pub get 21 | - name: 📢 Publish 22 | run: dart pub publish --force 23 | -------------------------------------------------------------------------------- /.github/workflows/site.yaml: -------------------------------------------------------------------------------- 1 | name: site 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - ".github/workflows/site.yaml" 7 | - "site/**" 8 | push: 9 | paths: 10 | - ".github/workflows/site.yaml" 11 | - "site/**" 12 | branches: 13 | - main 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | 19 | defaults: 20 | run: 21 | working-directory: site 22 | 23 | steps: 24 | - name: 📚 Git Checkout 25 | uses: actions/checkout@v4 26 | 27 | - name: ⚙️ Setup Node 28 | uses: actions/setup-node@v4 29 | with: 30 | node-version: 18.x 31 | cache: npm 32 | cache-dependency-path: site/package-lock.json 33 | 34 | - name: 📦 Install Dependencies 35 | run: npm ci 36 | 37 | - name: ✨ Check Format 38 | run: npm run format:check 39 | 40 | - name: 🧹 Lint 41 | run: npm run lint 42 | 43 | - name: 👷 Build website 44 | run: npm run build 45 | 46 | deploy: 47 | needs: build 48 | 49 | runs-on: ubuntu-latest 50 | 51 | if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} 52 | 53 | defaults: 54 | run: 55 | working-directory: site 56 | 57 | steps: 58 | - name: 📚 Git Checkout 59 | uses: actions/checkout@v4 60 | 61 | - name: ⚙️ Setup Node 62 | uses: actions/setup-node@v4 63 | with: 64 | node-version: 18.x 65 | cache: npm 66 | cache-dependency-path: site/package-lock.json 67 | 68 | - name: 📦 Install Dependencies 69 | run: npm ci 70 | 71 | - name: ✨ Check Format 72 | run: npm run format:check 73 | 74 | - name: 🧹 Lint 75 | run: npm run lint 76 | 77 | - name: 👷 Build website 78 | run: npm run build 79 | 80 | - name: ☁️ Deploy to GitHub Pages 81 | uses: peaceiris/actions-gh-pages@v4 82 | with: 83 | deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }} 84 | publish_dir: ./site/build 85 | user_name: github-actions[bot] 86 | user_email: 41898282+github-actions[bot]@users.noreply.github.com 87 | -------------------------------------------------------------------------------- /.github/workflows/spdx_license.yaml: -------------------------------------------------------------------------------- 1 | name: spdx_license 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | push: 9 | paths: 10 | - .github/workflows/spdx_license.yaml 11 | - "tool/spdx_license/**" 12 | branches: 13 | - main 14 | pull_request: 15 | paths: 16 | - .github/workflows/spdx_license.yaml 17 | - "tool/spdx_license/**" 18 | branches: 19 | - main 20 | 21 | jobs: 22 | build_hooks: 23 | defaults: 24 | run: 25 | working-directory: tool/spdx_license/hooks 26 | 27 | runs-on: ubuntu-latest 28 | 29 | # This job can be replaced by VeryGoodOpenSource/very_good_workflows/.github/workflows/dart_package.yml@v1, 30 | # once the following issue is resolved: 31 | # https://github.com/VeryGoodOpenSource/very_good_workflows/issues/150 32 | steps: 33 | - name: 📚 Git Checkout 34 | uses: actions/checkout@v4 35 | 36 | - name: 🎯 Setup Dart 37 | uses: dart-lang/setup-dart@v1 38 | with: 39 | sdk: 3.5.0 40 | 41 | - name: 📦 Install Dependencies 42 | run: dart pub get 43 | 44 | - name: ✨ Check Formatting 45 | run: dart format --set-exit-if-changed . 46 | 47 | - name: 🕵️ Analyze 48 | run: dart analyze --fatal-infos --fatal-warnings 49 | 50 | - name: 🧪 Run Tests 51 | run: | 52 | dart pub global activate coverage 1.2.0 53 | dart pub run test -j 4 --run-skipped --coverage=coverage --test-randomize-ordering-seed random && dart pub global run coverage:format_coverage --lcov --in=coverage --out=coverage/lcov.info --packages=.dart_tool/package_config.json --report-on="pre_gen,post_gen" 54 | 55 | - name: 📊 Check Code Coverage 56 | uses: VeryGoodOpenSource/very_good_coverage@v3.0.0 57 | with: 58 | path: tool/spdx_license/hooks/coverage/lcov.info 59 | 60 | build_brick: 61 | defaults: 62 | run: 63 | working-directory: tool/spdx_license 64 | 65 | runs-on: ubuntu-latest 66 | 67 | steps: 68 | - name: 📚 Git Checkout 69 | uses: actions/checkout@v4 70 | 71 | - name: 🎯 Setup Dart 72 | uses: dart-lang/setup-dart@v1 73 | with: 74 | sdk: 3.5.0 75 | 76 | - name: 📦 Install Dependencies 77 | run: dart pub get 78 | 79 | - name: 🧱 Mason make 80 | # If this step fails, you may need to run the following command (from tool/spdx_license): 81 | # ```sh 82 | # mason make spdx_license --licenses "[]" -o test --on-conflict overwrite 83 | # ``` 84 | # This will build the brick and generate the latest files for testing. 85 | run: | 86 | dart pub global activate mason_cli 87 | mason get 88 | mason make spdx_license --licenses "[]" -o test --on-conflict overwrite --set-exit-if-changed 89 | 90 | - name: ✨ Check Formatting 91 | run: dart format --set-exit-if-changed test/**_test.dart 92 | 93 | - name: 🕵️ Analyze 94 | run: dart analyze --fatal-infos --fatal-warnings test/**_test.dart 95 | 96 | - name: 🧪 Run Tests 97 | run: dart test -j 4 --run-skipped --test-randomize-ordering-seed random 98 | -------------------------------------------------------------------------------- /.github/workflows/spdx_license_bot.yaml: -------------------------------------------------------------------------------- 1 | name: spdx_license_bot 2 | 3 | on: 4 | # This should ideally trigger whenever there is a commit to the [SPDX License repository](https://github.com/spdx/license-list-data). 5 | # However, this is not yet possible see: https://github.com/orgs/community/discussions/26323 6 | schedule: 7 | # At 08:04 on every day-of-week from Monday through Friday. 8 | - cron: "4 8 * * 1-5" 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build: 13 | defaults: 14 | run: 15 | working-directory: tool/spdx_license 16 | 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - name: 📚 Git Checkout 21 | uses: actions/checkout@v4 22 | 23 | - name: 🎯 Setup Dart 24 | uses: dart-lang/setup-dart@v1 25 | 26 | - name: 📦 Install Dependencies 27 | run: dart pub get 28 | 29 | - name: 💻 Install Mason 30 | run: | 31 | dart pub global activate mason_cli 32 | mason get 33 | 34 | - name: 🧱 Mason Make (tool/spdx_license/test) 35 | id: make 36 | run: if [[ $(mason make spdx_license -q --licenses "[]" -o test --on-conflict overwrite --set-exit-if-changed) =~ "0 files changed" ]]; then echo "did_change=false"; else echo "did_change=true"; fi >> $GITHUB_ENV 37 | 38 | - name: 🔑 Config Git User 39 | if: ${{ env.did_change == 'true' }} 40 | run: | 41 | git config user.name VGV Bot 42 | git config user.email vgvbot@users.noreply.github.com 43 | 44 | - name: 🧱 Mason Make (lib/pub_license/spdx_license) 45 | if: ${{ env.did_change == 'true' }} 46 | run: | 47 | cd ../.. 48 | mason make spdx_license -o lib/src/pub_license/ --on-conflict=overwrite --licenses "[]" 49 | dart format lib 50 | cd tool/spdx_license 51 | 52 | - name: 📝 Create Pull Request 53 | if: ${{ env.did_change == 'true' }} 54 | uses: peter-evans/create-pull-request@v7.0.6 55 | with: 56 | base: main 57 | branch: chore/update-spdx-license 58 | commit-message: "chore: update SPDX licenses" 59 | title: "chore: update SPDX licenses" 60 | body: Please squash and merge me! 61 | labels: bot 62 | author: VGV Bot 63 | assignees: vgvbot 64 | committer: VGV Bot 65 | -------------------------------------------------------------------------------- /.github/workflows/sync_labels.yaml: -------------------------------------------------------------------------------- 1 | name: ♻️ Sync Labels 2 | 3 | on: 4 | push: 5 | paths: 6 | - .github/labels.yml 7 | branches: 8 | - main 9 | workflow_dispatch: 10 | 11 | jobs: 12 | labels: 13 | name: ♻️ Sync labels 14 | runs-on: ubuntu-20.04 15 | steps: 16 | - name: ⤵️ Check out code from GitHub 17 | uses: actions/checkout@v4 18 | 19 | - name: 🚀 Run Label Sync 20 | uses: srealmoreno/label-sync-action@v2 21 | with: 22 | config-file: | 23 | .github/labels.yml 24 | https://raw.githubusercontent.com/VeryGoodOpenSource/.github/main/.github/labels.yml 25 | -------------------------------------------------------------------------------- /.github/workflows/test_optimizer.yaml: -------------------------------------------------------------------------------- 1 | name: test_optimizer_ci 2 | 3 | concurrency: 4 | group: ${{ github.workflow }}-${{ github.ref }} 5 | cancel-in-progress: true 6 | 7 | on: 8 | push: 9 | paths: 10 | - .github/workflows/test_optimizer.yaml 11 | - "bricks/test_optimizer/**" 12 | branches: 13 | - main 14 | pull_request: 15 | paths: 16 | - .github/workflows/test_optimizer.yaml 17 | - "bricks/test_optimizer/**" 18 | branches: 19 | - main 20 | 21 | jobs: 22 | build_hooks: 23 | uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/dart_package.yml@v1 24 | with: 25 | dart_sdk: 3.5.0 26 | working_directory: bricks/test_optimizer/hooks 27 | 28 | verify_bundle: 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v4 32 | 33 | - uses: dart-lang/setup-dart@v1 34 | 35 | - name: Install mason 36 | run: dart pub global activate mason_cli 37 | 38 | - name: Run bundle generate 39 | run: tool/generate_test_optimizer_bundle.sh 40 | 41 | - name: Check for unbundled changes 42 | run: git diff --exit-code --quiet || { echo "::error::Changes detected on the test_optimizer brick. Please run tool/generate_test_optimizer_bundle.sh to bundle these changes"; exit 1; } 43 | -------------------------------------------------------------------------------- /.github/workflows/very_good_cli.yaml: -------------------------------------------------------------------------------- 1 | name: very_good_cli 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - ".github/workflows/very_good_cli.yaml" 7 | - "lib/**" 8 | - "test/**" 9 | - "pubspec.yaml" 10 | push: 11 | branches: 12 | - main 13 | paths: 14 | - ".github/workflows/very_good_cli.yaml" 15 | - "lib/**" 16 | - "test/**" 17 | - "pubspec.yaml" 18 | 19 | jobs: 20 | build: 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | 26 | - uses: subosito/flutter-action@v2.8.0 27 | with: 28 | flutter-version: 3.24.0 29 | 30 | - name: Install Dependencies 31 | run: flutter pub get 32 | 33 | - name: Format 34 | run: dart format --set-exit-if-changed lib test 35 | 36 | - name: Analyze 37 | run: flutter analyze lib test 38 | 39 | - name: Verify Build 40 | run: flutter pub run test --run-skipped -t pull-request-only 41 | 42 | - name: Run Tests 43 | run: | 44 | flutter pub global activate coverage 1.2.0 45 | flutter pub run test -j 1 -x pull-request-only -x e2e --coverage=coverage --test-randomize-ordering-seed random && dart pub global run coverage:format_coverage --lcov --in=coverage --out=coverage/lcov.info --packages=.dart_tool/package_config.json --report-on=lib 46 | 47 | - name: Check Code Coverage 48 | uses: VeryGoodOpenSource/very_good_coverage@v3.0.0 49 | with: 50 | exclude: "**/*.gen.dart" 51 | 52 | pana: 53 | runs-on: ubuntu-latest 54 | 55 | steps: 56 | - uses: actions/checkout@v4 57 | 58 | - uses: subosito/flutter-action@v2.8.0 59 | 60 | - name: Install Dependencies 61 | run: | 62 | flutter packages get 63 | flutter pub global activate pana 64 | 65 | - name: Verify Pub Score 66 | run: | 67 | PANA=$(pana . --no-warning); PANA_SCORE=$(echo $PANA | sed -n "s/.*Points: \([0-9]*\)\/\([0-9]*\)./\1\/\2/p") 68 | echo "score: $PANA_SCORE" 69 | IFS='/'; read -a SCORE_ARR <<< "$PANA_SCORE"; SCORE=SCORE_ARR[0]; TOTAL=SCORE_ARR[1] 70 | if (( $SCORE < $TOTAL )); then echo "minimum score not met!"; exit 1; fi 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub 2 | .dart_tool/ 3 | .packages 4 | pubspec.lock 5 | 6 | # Conventional directory for build outputs 7 | build/ 8 | 9 | # Directory created by dartdoc 10 | doc/api/ 11 | 12 | # Temporary Files 13 | .tmp/ 14 | 15 | # Files generated during tests 16 | .test_coverage.dart 17 | coverage/ 18 | .test_optimizer.dart 19 | !bricks/test_optimizer/__brick__/test/.test_optimizer.dart 20 | 21 | # Android studio and IntelliJ 22 | .idea 23 | 24 | # Misc files 25 | .DS_Store 26 | 27 | # Files generated by Mason 28 | mason-lock.json 29 | .mason -------------------------------------------------------------------------------- /.pubignore: -------------------------------------------------------------------------------- 1 | tool/ -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at hello@verygood.ventures. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faqs 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Very Good Ventures 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:very_good_analysis/analysis_options.5.1.0.yaml 2 | analyzer: 3 | exclude: 4 | - "**/version.dart" 5 | - "bricks/**/__brick__" 6 | -------------------------------------------------------------------------------- /bin/very_good.dart: -------------------------------------------------------------------------------- 1 | import 'package:universal_io/io.dart'; 2 | import 'package:very_good_cli/src/command_runner.dart'; 3 | 4 | Future main(List args) async { 5 | await _flushThenExit(await VeryGoodCommandRunner().run(args)); 6 | } 7 | 8 | /// Flushes the stdout and stderr streams, then exits the program with the given 9 | /// status code. 10 | /// 11 | /// This returns a Future that will never complete, since the program will have 12 | /// exited already. This is useful to prevent Future chains from proceeding 13 | /// after you've decided to exit. 14 | Future _flushThenExit(int status) { 15 | return Future.wait([stdout.close(), stderr.close()]) 16 | .then((_) => exit(status)); 17 | } 18 | -------------------------------------------------------------------------------- /bricks/test_optimizer/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## 🦄 Contributing to the Very Good CLI test optimizer 2 | 3 | First of all, thank you for taking the time to contribute! 🎉👍 Before you do, please carefully read this guide. 4 | 5 | ## Developing for Very Good CLI's test optimizer 6 | 7 | To develop for Very Good CLI's test optimizer, you will also need to become familiar with our processes and conventions detailed [here](../../CONTRIBUTING.md). 8 | 9 | 💡 **Note**: The test optimizer brick is not published at [Brick Hub](brickhub.dev). It is not intended to be used by the general public. Instead, it has been designed to work closely with Very Good CLI's `test` command. 10 | 11 | ### Setting up your local development environment 12 | 13 | 1. Install a valid [Dart SDK](https://dart.dev/get-dart) in your local environment. Compatible Dart SDK versions with test optimizer can be found [here](https://github.com/VeryGoodOpenSource/very_good_cli/blob/main/bricks/test_optimizer/hooks/pubspec.yaml). If you have Flutter installed you likely have a valid Dart SDK version already installed. 14 | 15 | 2. Install [Mason](https://github.com/felangel/mason/tree/master/packages/mason_cli#installation) in your local environment: 16 | 17 | ```sh 18 | # 🎯 Activate from https://pub.dev 19 | dart pub global activate mason_cli 20 | ``` 21 | 22 | 3. Get hooks' dependencies: 23 | 24 | ```sh 25 | # 🪝 Get hooks' dependencies (from bricks/test_optimizer/hooks) 26 | dart pub get 27 | ``` 28 | 29 | 4. Run all test optimizer tests: 30 | 31 | ```sh 32 | # 🪝 Run test optimizer hooks' unit test (from bricks/test_optimizer/hooks) 33 | dart test 34 | 35 | # 💻 Run `very_good test` end to end tests (from e2e/) 36 | dart test test/src/commands/test/async_main_test.dart && 37 | dart test test/src/commands/test/no_project_test.dart && 38 | dart test test/src/commands/test/spaced_golden_file_name.dart 39 | ``` 40 | 41 | If not all test passed out of the box please submit an [issue](https://github.com/VeryGoodOpenSource/very_good_cli/issues/new/choose) so it can get fixed. 42 | 43 | 5. Install your own version of test optimizer in your local environment: 44 | 45 | ```sh 46 | # 🧱 Adds test optimizer brick from path (from bricks/test_optimizer) 47 | mason add --global test_optimizer --path . 48 | ``` 49 | 50 | Then, you can start using it: 51 | 52 | ```sh 53 | # 🚀 Try test optimizer locally 54 | mason make test_optimizer 55 | ``` 56 | 57 | 6. If you want to run your test optimizer with Very Good CLI (like for example `very_good test`) locally: 58 | 59 | ```sh 60 | # 📦 Bundle test optimizer (from root) 61 | tool/generate_test_optimizer_bundle.sh 62 | 63 | # 💻 Install your own version of Very Good CLI in your local environment (from root) 64 | dart pub global activate --source path . 65 | ``` 66 | 67 | 💡 **Note**: After changing the test optimizer brick, make sure to always generate a new test optimizer bundle and commit this as part of your pull request. 68 | -------------------------------------------------------------------------------- /bricks/test_optimizer/README.md: -------------------------------------------------------------------------------- 1 | # test_optimizer 2 | 3 | [![Powered by Mason](https://img.shields.io/endpoint?url=https%3A%2F%2Ftinyurl.com%2Fmason-badge)](https://github.com/felangel/mason) 4 | 5 | A brick that generates a single entrypoint for Dart tests. 6 | 7 | _Generated by [mason][1] 🧱_ 8 | 9 | ## Getting Started 🚀 10 | 11 | ```sh 12 | mason make test_optimizer --package-root ./path/to/package --on-conflict overwrite 13 | ``` 14 | 15 | The above command will generate a `.test_optimizer.dart` in the `test` directory that imports and executes all tests 16 | 17 | ```dart 18 | // GENERATED CODE - DO NOT MODIFY BY HAND 19 | // Consider adding this file to your .gitignore. 20 | 21 | import 'app/view/app_test.dart' as _a; 22 | import 'counter/cubit/counter_cubit_test.dart' as _b; 23 | import 'counter/view/counter_page_test.dart' as _c; 24 | 25 | void main() { 26 | group('app_view_app_test_dart', () { _a.main(); }); 27 | group('counter_cubit_counter_cubit_test_dart', () { _b.main(); }); 28 | group('counter_view_counter_page_test_dart', () { _c.main(); }); 29 | } 30 | ``` 31 | 32 | [1]: https://github.com/felangel/mason 33 | -------------------------------------------------------------------------------- /bricks/test_optimizer/__brick__/test/.test_optimizer.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | // Consider adding this file to your .gitignore. 3 | 4 | {{#isFlutter}}import 'dart:io'; 5 | import 'dart:typed_data'; 6 | 7 | import 'package:flutter_test/flutter_test.dart'; 8 | {{/isFlutter}}{{^isFlutter}}import 'package:test/test.dart';{{/isFlutter}} 9 | 10 | {{#tests}}import '{{{path}}}' as {{identifier}}; 11 | {{/tests}} 12 | void main() { 13 | {{#isFlutter}} goldenFileComparator = _TestOptimizationAwareGoldenFileComparator(goldenFileComparator as LocalFileComparator);{{/isFlutter}} 14 | {{#tests}} group('{{{path}}}', () { {{identifier}}.main(); }); 15 | {{/tests}}} 16 | 17 | {{#isFlutter}} 18 | class _TestOptimizationAwareGoldenFileComparator extends GoldenFileComparator { 19 | final List goldenFilePaths; 20 | final LocalFileComparator previousGoldenFileComparator; 21 | 22 | _TestOptimizationAwareGoldenFileComparator(this.previousGoldenFileComparator) 23 | : goldenFilePaths = _goldenFilePaths; 24 | 25 | static List get _goldenFilePaths => 26 | Directory.fromUri((goldenFileComparator as LocalFileComparator).basedir) 27 | .listSync(recursive: true, followLinks: true) 28 | .whereType() 29 | .map((file) => file.path) 30 | .where((path) => path.endsWith('.png')) 31 | .toList(); 32 | @override 33 | Future compare(Uint8List imageBytes, Uri golden) => previousGoldenFileComparator.compare(imageBytes, golden); 34 | 35 | @override 36 | Uri getTestUri(Uri key, int? version) { 37 | final keyString = key.toFilePath(); 38 | return Uri.parse(goldenFilePaths 39 | .singleWhere((goldenFilePath) => goldenFilePath.endsWith(keyString))); 40 | } 41 | 42 | @override 43 | Future update(Uri golden, Uint8List imageBytes) => previousGoldenFileComparator.update(golden, imageBytes); 44 | 45 | } 46 | {{/isFlutter}} -------------------------------------------------------------------------------- /bricks/test_optimizer/brick.yaml: -------------------------------------------------------------------------------- 1 | name: test_optimizer 2 | description: A brick that generates a single entrypoint for Dart tests. 3 | version: 0.1.0+1 4 | publish_to: none 5 | 6 | environment: 7 | mason: ^0.1.0 8 | 9 | vars: 10 | package-root: 11 | type: string 12 | default: "." 13 | description: The path to the package root. 14 | prompt: Please enter the path to the package root. 15 | -------------------------------------------------------------------------------- /bricks/test_optimizer/hooks/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:very_good_analysis/analysis_options.7.0.0.yaml 2 | linter: 3 | rules: 4 | public_member_api_docs: false 5 | -------------------------------------------------------------------------------- /bricks/test_optimizer/hooks/lib/dart_identifier_generator.dart: -------------------------------------------------------------------------------- 1 | /// {@template dart_identifier_generator} 2 | /// A class that generates valid Dart identifiers. 3 | /// 4 | /// See also: 5 | /// 6 | /// * Section 17.37 from [Dart Language Specification](https://dart.dev/guides/language/specifications/DartLangSpec-v2.10.pdf) 7 | /// {@endtemplate} 8 | class DartIdentifierGenerator { 9 | /// {@macro dart_identifier_generator} 10 | DartIdentifierGenerator([ 11 | this._chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 12 | ]) : _nextId = [0]; 13 | 14 | final String _chars; 15 | final List _nextId; 16 | 17 | /// Generate the next short identifier. 18 | String next() { 19 | final r = ['_', for (final char in _nextId) _chars[char]]; 20 | _increment(); 21 | return r.join(); 22 | } 23 | 24 | void _increment() { 25 | for (var i = 0; i < _nextId.length; i++) { 26 | final val = ++_nextId[i]; 27 | if (val >= _chars.length) { 28 | _nextId[i] = 0; 29 | } else { 30 | return; 31 | } 32 | } 33 | _nextId.add(0); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /bricks/test_optimizer/hooks/lib/pre_gen.dart: -------------------------------------------------------------------------------- 1 | // No need for documentation in brick hooks 2 | // ignore_for_file: public_member_api_docs 3 | 4 | import 'dart:io'; 5 | 6 | import 'package:hooks/dart_identifier_generator.dart'; 7 | import 'package:mason/mason.dart'; 8 | import 'package:path/path.dart' as path; 9 | 10 | typedef ExitFn = Never Function(int code); 11 | 12 | ExitFn exitFn = exit; 13 | 14 | Future run(HookContext context) async { 15 | final packageRoot = context.vars['package-root'] as String; 16 | final testDir = Directory(path.join(packageRoot, 'test')); 17 | 18 | if (!testDir.existsSync()) { 19 | context.logger.err('Could not find directory ${testDir.path}'); 20 | exitFn(1); 21 | } 22 | 23 | final pubspec = File(path.join(packageRoot, 'pubspec.yaml')); 24 | if (!pubspec.existsSync()) { 25 | context.logger.err('Could not find pubspec.yaml at ${testDir.path}'); 26 | exitFn(1); 27 | } 28 | 29 | final pubspecContents = await pubspec.readAsString(); 30 | final flutterSdkRegExp = RegExp(r'sdk:\s*flutter$', multiLine: true); 31 | final isFlutter = flutterSdkRegExp.hasMatch(pubspecContents); 32 | 33 | final identifierGenerator = DartIdentifierGenerator(); 34 | final testIdentifierTable = >[]; 35 | for (final entity 36 | in testDir.listSync(recursive: true).where((entity) => entity.isTest)) { 37 | final relativePath = 38 | path.relative(entity.path, from: testDir.path).replaceAll(r'\', '/'); 39 | testIdentifierTable.add({ 40 | 'path': relativePath, 41 | 'identifier': identifierGenerator.next(), 42 | }); 43 | } 44 | 45 | context.vars = {'tests': testIdentifierTable, 'isFlutter': isFlutter}; 46 | } 47 | 48 | extension on FileSystemEntity { 49 | bool get isTest { 50 | return this is File && path.basename(this.path).endsWith('_test.dart'); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /bricks/test_optimizer/hooks/pre_gen.dart: -------------------------------------------------------------------------------- 1 | import 'package:mason/mason.dart'; 2 | 3 | import 'lib/pre_gen.dart' as pre_gen; 4 | 5 | Future run(HookContext context) async { 6 | await pre_gen.run(context); 7 | } 8 | -------------------------------------------------------------------------------- /bricks/test_optimizer/hooks/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: hooks 2 | publish_to: none 3 | 4 | environment: 5 | sdk: ^3.5.0 6 | 7 | dependencies: 8 | mason: ^0.1.0 9 | path: ^1.8.1 10 | 11 | # Beware: on hooks, even dev dependencies have to be compatible to all dart versions covered by 12 | # the sdk constraints above 13 | dev_dependencies: 14 | mocktail: ^1.0.0 15 | test: ^1.25.0 16 | very_good_analysis: ^7.0.0 17 | -------------------------------------------------------------------------------- /bricks/test_optimizer/hooks/test/dart_identifier_generator_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:hooks/dart_identifier_generator.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | group('$DartIdentifierGenerator', () { 6 | test('can be instantiated', () { 7 | expect(DartIdentifierGenerator.new, returnsNormally); 8 | }); 9 | 10 | group('next', () { 11 | test('returns normally', () { 12 | final generator = DartIdentifierGenerator(); 13 | expect(generator.next, returnsNormally); 14 | }); 15 | 16 | test('generates unique strings', () { 17 | final generator = DartIdentifierGenerator(); 18 | final ids = {}; 19 | const count = 1000; 20 | for (var i = 0; i < count; i++) { 21 | final id = generator.next(); 22 | ids.add(id); 23 | } 24 | expect(ids.length, count); 25 | }); 26 | 27 | test('generates valid dart identifiers', () { 28 | // For a full specification of valid dart identifiers, read 29 | // Section 17.37 from the [Dart Language Specification](https://dart.dev/guides/language/specifications/DartLangSpec-v2.10.pdf). 30 | final generator = DartIdentifierGenerator(); 31 | final ids = []; 32 | for (var i = 0; i < 1000; i++) { 33 | final id = generator.next(); 34 | ids.add(id); 35 | } 36 | 37 | expect( 38 | ids.where((id) => _dartReservedKeywords.contains(id)), 39 | isEmpty, 40 | ); 41 | expect( 42 | ids.every((id) { 43 | final idStart = id.codeUnitAt(0); 44 | final isAlphabetic = (idStart >= 65 && idStart <= 90) || 45 | (idStart >= 97 && idStart <= 122); 46 | final isUnderscore = idStart == 95; 47 | final isDollarSign = idStart == 36; 48 | return isAlphabetic || isUnderscore || isDollarSign; 49 | }), 50 | true, 51 | ); 52 | expect( 53 | ids.every((id) { 54 | final idPart = id.codeUnits.skip(1); 55 | return idPart.every((ascii) { 56 | final isAlphabetic = 57 | (ascii >= 65 && ascii <= 90) || (ascii >= 97 && ascii <= 122); 58 | final isUnderscore = ascii == 95; 59 | final isDollarSign = ascii == 36; 60 | final isDigit = ascii >= 48 && ascii <= 57; 61 | return isAlphabetic || isUnderscore || isDollarSign || isDigit; 62 | }); 63 | }), 64 | true, 65 | ); 66 | }); 67 | }); 68 | }); 69 | } 70 | 71 | // All reserved keywords in [Dart 2.19.2](https://dart.dev/guides/language/language-tour#keywords). 72 | const _dartReservedKeywords = [ 73 | 'abstract', 74 | 'as', 75 | 'assert', 76 | 'async', 77 | 'await', 78 | 'break', 79 | 'case', 80 | 'catch', 81 | 'class', 82 | 'const', 83 | 'continue', 84 | 'covariant', 85 | 'default', 86 | 'deferred', 87 | 'do', 88 | 'dynamic', 89 | 'else', 90 | 'enum', 91 | 'export', 92 | 'extends', 93 | 'extension', 94 | 'external', 95 | 'factory', 96 | 'false', 97 | 'final', 98 | 'finally', 99 | 'for', 100 | 'Function', 101 | 'get', 102 | 'hide', 103 | 'if', 104 | 'implements', 105 | 'import', 106 | 'in', 107 | 'interface', 108 | 'is', 109 | 'late', 110 | 'library', 111 | 'mixin', 112 | 'new', 113 | 'null', 114 | 'on', 115 | 'operator', 116 | 'part', 117 | 'required', 118 | 'rethrow', 119 | 'return', 120 | 'set', 121 | 'show', 122 | 'static', 123 | 'super', 124 | 'switch', 125 | 'sync', 126 | 'this', 127 | 'throw', 128 | 'true', 129 | 'try', 130 | 'typedef', 131 | 'var', 132 | 'void', 133 | 'while', 134 | 'with', 135 | 'yield', 136 | ]; 137 | -------------------------------------------------------------------------------- /coverage_badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | coverage 16 | coverage 17 | 100% 18 | 100% 19 | 20 | -------------------------------------------------------------------------------- /dart_test.yaml: -------------------------------------------------------------------------------- 1 | tags: 2 | pull-request-only: 3 | skip: "Should only be run during pull request" 4 | -------------------------------------------------------------------------------- /doc/assets/very_good_create.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VeryGoodOpenSource/very_good_cli/06daf69e0ac171d2ebdedeaccd815f9f680cd4cb/doc/assets/very_good_create.gif -------------------------------------------------------------------------------- /doc/assets/very_good_create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VeryGoodOpenSource/very_good_cli/06daf69e0ac171d2ebdedeaccd815f9f680cd4cb/doc/assets/very_good_create.png -------------------------------------------------------------------------------- /doc/assets/vgv_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VeryGoodOpenSource/very_good_cli/06daf69e0ac171d2ebdedeaccd815f9f680cd4cb/doc/assets/vgv_logo.png -------------------------------------------------------------------------------- /e2e/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://www.dartlang.org/guides/libraries/private-files 2 | 3 | # Files and directories created by pub 4 | .dart_tool/ 5 | .packages 6 | build/ 7 | pubspec.lock -------------------------------------------------------------------------------- /e2e/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:very_good_analysis/analysis_options.7.0.0.yaml 2 | -------------------------------------------------------------------------------- /e2e/helpers/command_helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:mason/mason.dart'; 4 | import 'package:mocktail/mocktail.dart'; 5 | import 'package:pub_updater/pub_updater.dart'; 6 | import 'package:very_good_cli/src/command_runner.dart'; 7 | 8 | class _MockLogger extends Mock implements Logger {} 9 | 10 | class _MockProgress extends Mock implements Progress {} 11 | 12 | class _MockPubUpdater extends Mock implements PubUpdater {} 13 | 14 | void Function() _overridePrint(void Function(List) fn) { 15 | return () { 16 | final printLogs = []; 17 | final spec = ZoneSpecification( 18 | print: (_, __, ___, String msg) { 19 | printLogs.add(msg); 20 | }, 21 | ); 22 | 23 | return Zone.current 24 | .fork(specification: spec) 25 | .run(() => fn(printLogs)); 26 | }; 27 | } 28 | 29 | void Function() withRunner( 30 | FutureOr Function( 31 | VeryGoodCommandRunner commandRunner, 32 | Logger logger, 33 | PubUpdater pubUpdater, 34 | List printLogs, 35 | ) runnerFn, 36 | ) { 37 | return _overridePrint((printLogs) async { 38 | final logger = _MockLogger(); 39 | final progress = _MockProgress(); 40 | final pubUpdater = _MockPubUpdater(); 41 | final progressLogs = []; 42 | final commandRunner = VeryGoodCommandRunner( 43 | logger: logger, 44 | pubUpdater: pubUpdater, 45 | environment: {'CI': 'true'}, 46 | ); 47 | 48 | when(() => progress.complete(any())).thenAnswer((_) { 49 | final message = _.positionalArguments.elementAt(0) as String?; 50 | if (message != null) progressLogs.add(message); 51 | }); 52 | when(() => logger.progress(any())).thenReturn(progress); 53 | when( 54 | () => pubUpdater.isUpToDate( 55 | packageName: any(named: 'packageName'), 56 | currentVersion: any(named: 'currentVersion'), 57 | ), 58 | ).thenAnswer((_) => Future.value(true)); 59 | 60 | await runnerFn(commandRunner, logger, pubUpdater, printLogs); 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /e2e/helpers/copy_directory.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:path/path.dart' as path; 4 | 5 | Future copyDirectory(Directory from, Directory to) async { 6 | await to.create(recursive: true); 7 | await for (final entity in from.list(recursive: true)) { 8 | final toPath = path.join( 9 | to.path, 10 | path.relative(entity.path, from: from.path), 11 | ); 12 | if (entity is Directory) { 13 | await Directory(toPath).create(); 14 | } else if (entity is File) { 15 | await entity.copy(toPath); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /e2e/helpers/expect_successful_process_result.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:mason/mason.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | Future expectSuccessfulProcessResult( 7 | String executable, 8 | List arguments, { 9 | required String workingDirectory, 10 | bool validateStderr = true, 11 | }) async { 12 | final result = await Process.run( 13 | executable, 14 | arguments, 15 | workingDirectory: workingDirectory, 16 | runInShell: true, 17 | ); 18 | expect( 19 | result.exitCode, 20 | equals(ExitCode.success.code), 21 | reason: 22 | '''`$executable ${arguments.join(' ')}` in $workingDirectory failed with "${result.stderr}" and "${result.stdout}"''', 23 | ); 24 | if (validateStderr) { 25 | expect(result.stderr, isEmpty); 26 | } 27 | 28 | return result; 29 | } 30 | -------------------------------------------------------------------------------- /e2e/helpers/helpers.dart: -------------------------------------------------------------------------------- 1 | export 'command_helper.dart'; 2 | export 'copy_directory.dart'; 3 | export 'expect_successful_process_result.dart'; 4 | -------------------------------------------------------------------------------- /e2e/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: very_good_cli_e2e 2 | description: End to End tests for package:very_good_cli 3 | version: 0.1.0+1 4 | publish_to: none 5 | 6 | environment: 7 | sdk: ^3.5.0 8 | 9 | dev_dependencies: 10 | mason: ^0.1.0 11 | mocktail: ^1.0.0 12 | path: ^1.8.0 13 | pub_updater: ^0.5.0 14 | test: ^1.25.0 15 | universal_io: ^2.0.4 16 | very_good_analysis: ^7.0.0 17 | very_good_cli: 18 | path: ../ 19 | -------------------------------------------------------------------------------- /e2e/test/commands/create/dart_cli/dart_cli_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:mason/mason.dart'; 2 | import 'package:path/path.dart' as path; 3 | import 'package:test/test.dart'; 4 | import 'package:universal_io/io.dart'; 5 | 6 | import '../../../../helpers/helpers.dart'; 7 | 8 | void main() { 9 | test( 10 | 'create dart_cli', 11 | timeout: const Timeout(Duration(minutes: 2)), 12 | withRunner((commandRunner, logger, updater, logs) async { 13 | final tempDirectory = Directory.systemTemp.createTempSync(); 14 | addTearDown(() => tempDirectory.deleteSync(recursive: true)); 15 | 16 | final result = await commandRunner.run( 17 | [ 18 | 'create', 19 | 'dart_cli', 20 | 'my_cli', 21 | '-o', 22 | tempDirectory.path, 23 | ], 24 | ); 25 | expect(result, equals(ExitCode.success.code)); 26 | 27 | final workingDirectory = path.join(tempDirectory.path, 'my_cli'); 28 | 29 | // add coverage to collect coverage on dart test 30 | await expectSuccessfulProcessResult( 31 | 'dart', 32 | ['pub', 'add', 'coverage:1.2.0'], 33 | workingDirectory: workingDirectory, 34 | ); 35 | 36 | await expectSuccessfulProcessResult( 37 | 'dart', 38 | ['format'], 39 | workingDirectory: workingDirectory, 40 | ); 41 | 42 | final analyzeResult = await expectSuccessfulProcessResult( 43 | 'flutter', 44 | ['analyze', '.'], 45 | workingDirectory: workingDirectory, 46 | ); 47 | expect(analyzeResult.stdout, contains('No issues found!')); 48 | 49 | final testResult = await expectSuccessfulProcessResult( 50 | 'dart', 51 | ['test', '--coverage=coverage', '--reporter=compact'], 52 | workingDirectory: workingDirectory, 53 | ); 54 | expect(testResult.stdout, contains('All tests passed!')); 55 | 56 | // collect coverage 57 | await expectSuccessfulProcessResult( 58 | 'dart', 59 | [ 60 | 'pub', 61 | 'run', 62 | 'coverage:format_coverage', 63 | '--lcov', 64 | '--in=coverage', 65 | '--out=coverage/lcov.info', 66 | '--packages=.dart_tool/package_config.json', 67 | '--report-on=lib', 68 | ], 69 | workingDirectory: workingDirectory, 70 | ); 71 | 72 | final testCoverageResult = await expectSuccessfulProcessResult( 73 | 'genhtml', 74 | ['coverage/lcov.info', '-o', 'coverage'], 75 | workingDirectory: workingDirectory, 76 | ); 77 | expect(testCoverageResult.stdout, contains('lines......: 100.0%')); 78 | }), 79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /e2e/test/commands/create/dart_package/dart_pkg_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:mason/mason.dart'; 2 | import 'package:path/path.dart' as path; 3 | import 'package:test/test.dart'; 4 | import 'package:universal_io/io.dart'; 5 | 6 | import '../../../../helpers/helpers.dart'; 7 | 8 | void main() { 9 | test( 10 | 'create dart_package', 11 | timeout: const Timeout(Duration(minutes: 2)), 12 | withRunner((commandRunner, logger, updater, logs) async { 13 | final tempDirectory = Directory.systemTemp.createTempSync(); 14 | addTearDown(() => tempDirectory.deleteSync(recursive: true)); 15 | 16 | final result = await commandRunner.run( 17 | ['create', 'dart_package', 'very_good_dart', '-o', tempDirectory.path], 18 | ); 19 | expect(result, equals(ExitCode.success.code)); 20 | 21 | final workingDirectory = path.join(tempDirectory.path, 'very_good_dart'); 22 | 23 | // add coverage to collect coverage on dart test 24 | await expectSuccessfulProcessResult( 25 | 'dart', 26 | ['pub', 'add', 'coverage:1.2.0'], 27 | workingDirectory: workingDirectory, 28 | ); 29 | 30 | await expectSuccessfulProcessResult( 31 | 'dart', 32 | ['format'], 33 | workingDirectory: workingDirectory, 34 | ); 35 | 36 | final analyzeResult = await expectSuccessfulProcessResult( 37 | 'flutter', 38 | ['analyze', '.'], 39 | workingDirectory: workingDirectory, 40 | ); 41 | expect(analyzeResult.stdout, contains('No issues found!')); 42 | 43 | final testResult = await expectSuccessfulProcessResult( 44 | 'dart', 45 | ['test', '--coverage=coverage', '--reporter=compact'], 46 | workingDirectory: workingDirectory, 47 | ); 48 | expect(testResult.stdout, contains('All tests passed!')); 49 | 50 | // collect coverage 51 | await expectSuccessfulProcessResult( 52 | 'dart', 53 | [ 54 | 'pub', 55 | 'run', 56 | 'coverage:format_coverage', 57 | '--lcov', 58 | '--in=coverage', 59 | '--out=coverage/lcov.info', 60 | '--packages=.dart_tool/package_config.json', 61 | '--report-on=lib', 62 | ], 63 | workingDirectory: workingDirectory, 64 | ); 65 | 66 | final testCoverageResult = await expectSuccessfulProcessResult( 67 | 'genhtml', 68 | ['coverage/lcov.info', '-o', 'coverage'], 69 | workingDirectory: workingDirectory, 70 | ); 71 | expect(testCoverageResult.stdout, contains('lines......: 100.0%')); 72 | }), 73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /e2e/test/commands/create/docs_site/docs_site_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:mason/mason.dart'; 2 | import 'package:path/path.dart' as path; 3 | import 'package:test/test.dart'; 4 | import 'package:universal_io/io.dart'; 5 | 6 | import '../../../../helpers/helpers.dart'; 7 | 8 | void main() { 9 | test( 10 | 'create docs_site', 11 | timeout: const Timeout(Duration(minutes: 2)), 12 | withRunner((commandRunner, logger, updater, logs) async { 13 | final tempDirectory = Directory.systemTemp.createTempSync(); 14 | addTearDown(() => tempDirectory.deleteSync(recursive: true)); 15 | 16 | final result = await commandRunner.run( 17 | [ 18 | 'create', 19 | 'docs_site', 20 | 'very_good_docs_site', 21 | '-o', 22 | tempDirectory.path, 23 | ], 24 | ); 25 | expect(result, equals(ExitCode.success.code)); 26 | 27 | final workingDirectory = 28 | path.join(tempDirectory.path, 'very_good_docs_site'); 29 | 30 | await expectSuccessfulProcessResult( 31 | 'npm', 32 | ['install'], 33 | workingDirectory: workingDirectory, 34 | validateStderr: false, 35 | ); 36 | 37 | await expectSuccessfulProcessResult( 38 | 'npm', 39 | ['run', 'format'], 40 | workingDirectory: workingDirectory, 41 | ); 42 | 43 | await expectSuccessfulProcessResult( 44 | 'npm', 45 | ['run', 'lint'], 46 | workingDirectory: workingDirectory, 47 | ); 48 | 49 | await expectSuccessfulProcessResult( 50 | 'npm', 51 | ['run', 'build'], 52 | workingDirectory: workingDirectory, 53 | ); 54 | }), 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /e2e/test/commands/create/flame_game/flame_game_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:mason/mason.dart'; 2 | import 'package:path/path.dart' as path; 3 | import 'package:test/test.dart'; 4 | import 'package:universal_io/io.dart'; 5 | 6 | import '../../../../helpers/helpers.dart'; 7 | 8 | void main() { 9 | test( 10 | 'create flame_game', 11 | timeout: const Timeout(Duration(minutes: 5)), 12 | withRunner((commandRunner, logger, updater, logs) async { 13 | final tempDirectory = Directory.systemTemp.createTempSync(); 14 | addTearDown(() => tempDirectory.deleteSync(recursive: true)); 15 | 16 | final result = await commandRunner.run( 17 | [ 18 | 'create', 19 | 'flame_game', 20 | 'very_good_flame_game', 21 | '-o', 22 | tempDirectory.path, 23 | ], 24 | ); 25 | expect(result, equals(ExitCode.success.code)); 26 | 27 | final workingDirectory = path.join( 28 | tempDirectory.path, 29 | 'very_good_flame_game', 30 | ); 31 | 32 | await expectSuccessfulProcessResult( 33 | 'dart', 34 | ['format'], 35 | workingDirectory: workingDirectory, 36 | ); 37 | 38 | final analyzeResult = await expectSuccessfulProcessResult( 39 | 'flutter', 40 | ['analyze', '.'], 41 | workingDirectory: workingDirectory, 42 | ); 43 | expect(analyzeResult.stdout, contains('No issues found!')); 44 | 45 | final testResult = await expectSuccessfulProcessResult( 46 | 'flutter', 47 | ['test', '--no-pub', '--coverage', '--reporter', 'compact'], 48 | workingDirectory: workingDirectory, 49 | ); 50 | expect(testResult.stdout, contains('All tests passed!')); 51 | 52 | final testCoverageResult = await expectSuccessfulProcessResult( 53 | 'genhtml', 54 | ['coverage/lcov.info', '-o', 'coverage'], 55 | workingDirectory: workingDirectory, 56 | ); 57 | expect(testCoverageResult.stdout, contains('lines......: 97.9%')); 58 | }), 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /e2e/test/commands/create/flutter_app/core_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:mason/mason.dart'; 2 | import 'package:path/path.dart' as path; 3 | import 'package:test/test.dart'; 4 | import 'package:universal_io/io.dart'; 5 | 6 | import '../../../../helpers/helpers.dart'; 7 | 8 | void main() { 9 | test( 10 | 'create flutter_app', 11 | timeout: const Timeout(Duration(minutes: 2)), 12 | withRunner((commandRunner, logger, updater, logs) async { 13 | final tempDirectory = Directory.systemTemp.createTempSync(); 14 | addTearDown(() => tempDirectory.deleteSync(recursive: true)); 15 | 16 | final result = await commandRunner.run( 17 | ['create', 'flutter_app', 'very_good_core', '-o', tempDirectory.path], 18 | ); 19 | expect(result, equals(ExitCode.success.code)); 20 | 21 | final workingDirectory = path.join(tempDirectory.path, 'very_good_core'); 22 | 23 | await expectSuccessfulProcessResult( 24 | 'dart', 25 | ['format'], 26 | workingDirectory: workingDirectory, 27 | ); 28 | 29 | final analyzeResult = await expectSuccessfulProcessResult( 30 | 'flutter', 31 | ['analyze', '.'], 32 | workingDirectory: workingDirectory, 33 | ); 34 | expect(analyzeResult.stdout, contains('No issues found!')); 35 | 36 | final testResult = await expectSuccessfulProcessResult( 37 | 'flutter', 38 | ['test', '--no-pub', '--coverage', '--reporter', 'compact'], 39 | workingDirectory: workingDirectory, 40 | ); 41 | expect(testResult.stdout, contains('All tests passed!')); 42 | 43 | final testCoverageResult = await expectSuccessfulProcessResult( 44 | 'genhtml', 45 | ['coverage/lcov.info', '-o', 'coverage'], 46 | workingDirectory: workingDirectory, 47 | ); 48 | expect(testCoverageResult.stdout, contains('lines......: 100.0%')); 49 | }), 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /e2e/test/commands/create/flutter_package/flutter_pkg_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:mason/mason.dart'; 2 | import 'package:path/path.dart' as path; 3 | import 'package:test/test.dart'; 4 | import 'package:universal_io/io.dart'; 5 | 6 | import '../../../../helpers/helpers.dart'; 7 | 8 | void main() { 9 | test( 10 | 'create flutter_package', 11 | timeout: const Timeout(Duration(minutes: 2)), 12 | withRunner((commandRunner, logger, updater, logs) async { 13 | final tempDirectory = Directory.systemTemp.createTempSync(); 14 | addTearDown(() => tempDirectory.deleteSync(recursive: true)); 15 | 16 | final result = await commandRunner.run( 17 | [ 18 | 'create', 19 | 'flutter_package', 20 | 'very_good_flutter', 21 | '-o', 22 | tempDirectory.path, 23 | ], 24 | ); 25 | expect(result, equals(ExitCode.success.code)); 26 | 27 | final workingDirectory = 28 | path.join(tempDirectory.path, 'very_good_flutter'); 29 | 30 | await expectSuccessfulProcessResult( 31 | 'dart', 32 | ['format'], 33 | workingDirectory: workingDirectory, 34 | ); 35 | 36 | final analyzeResult = await expectSuccessfulProcessResult( 37 | 'flutter', 38 | ['analyze', '.'], 39 | workingDirectory: workingDirectory, 40 | ); 41 | expect(analyzeResult.stdout, contains('No issues found!')); 42 | 43 | final testResult = await expectSuccessfulProcessResult( 44 | 'flutter', 45 | ['test', '--no-pub', '--coverage', '--reporter', 'compact'], 46 | workingDirectory: workingDirectory, 47 | ); 48 | expect(testResult.stdout, contains('All tests passed!')); 49 | 50 | final testCoverageResult = await expectSuccessfulProcessResult( 51 | 'genhtml', 52 | ['coverage/lcov.info', '-o', 'coverage'], 53 | workingDirectory: workingDirectory, 54 | ); 55 | expect(testCoverageResult.stdout, contains('lines......: 100.0%')); 56 | }), 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /e2e/test/commands/create/flutter_plugin/flutter_plugin_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:mason/mason.dart'; 2 | import 'package:path/path.dart' as path; 3 | import 'package:test/test.dart'; 4 | import 'package:universal_io/io.dart'; 5 | 6 | import '../../../../helpers/helpers.dart'; 7 | 8 | void main() { 9 | test( 10 | 'create flutter_plugin', 11 | timeout: const Timeout(Duration(minutes: 8)), 12 | withRunner((commandRunner, logger, updater, logs) async { 13 | final tempDirectory = Directory.systemTemp.createTempSync(); 14 | addTearDown(() => tempDirectory.deleteSync(recursive: true)); 15 | 16 | const pluginName = 'my_plugin'; 17 | final pluginDirectory = path.join(tempDirectory.path, pluginName); 18 | 19 | final result = await commandRunner.run( 20 | ['create', 'flutter_plugin', pluginName, '-o', tempDirectory.path], 21 | ); 22 | expect( 23 | result, 24 | equals(ExitCode.success.code), 25 | reason: '`very_good create flutter_plugin` failed with $result', 26 | ); 27 | 28 | await expectSuccessfulProcessResult( 29 | 'dart', 30 | ['format'], 31 | workingDirectory: pluginDirectory, 32 | ); 33 | 34 | final analyzeResult = await expectSuccessfulProcessResult( 35 | 'flutter', 36 | ['analyze', '.'], 37 | workingDirectory: pluginDirectory, 38 | ); 39 | expect(analyzeResult.stdout, contains('No issues found!')); 40 | 41 | final packageDirectories = [ 42 | path.join(pluginDirectory, pluginName), 43 | path.join(pluginDirectory, '${pluginName}_android'), 44 | path.join(pluginDirectory, '${pluginName}_ios'), 45 | path.join(pluginDirectory, '${pluginName}_linux'), 46 | path.join(pluginDirectory, '${pluginName}_macos'), 47 | path.join(pluginDirectory, '${pluginName}_web'), 48 | path.join(pluginDirectory, '${pluginName}_windows'), 49 | path.join(pluginDirectory, '${pluginName}_platform_interface'), 50 | ]; 51 | 52 | for (final packageDirectory in packageDirectories) { 53 | final testResult = await expectSuccessfulProcessResult( 54 | 'flutter', 55 | ['test', '--no-pub', '--coverage', '--reporter', 'compact'], 56 | workingDirectory: packageDirectory, 57 | ); 58 | expect(testResult.stdout, contains('All tests passed!')); 59 | 60 | final testCoverageResult = await expectSuccessfulProcessResult( 61 | 'genhtml', 62 | ['coverage/lcov.info', '-o', 'coverage'], 63 | workingDirectory: packageDirectory, 64 | ); 65 | expect(testCoverageResult.stdout, contains('lines......: 100.0%')); 66 | } 67 | }), 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /e2e/test/commands/packages/check/licenses/licenses_allowed_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:mason/mason.dart'; 4 | import 'package:path/path.dart' as path; 5 | import 'package:test/test.dart'; 6 | 7 | import '../../../../../helpers/helpers.dart'; 8 | 9 | /// Objectives: 10 | /// 11 | /// * Generate a new Dart project using (`dart create`) 12 | /// * Add dependencies to `pubspec.yaml` with an MIT license 13 | /// * Run `very_good packages check licenses --allowed="MIT"` and expect success 14 | void main() { 15 | test( 16 | 'packages check licenses --allowed="MIT"', 17 | timeout: const Timeout(Duration(minutes: 2)), 18 | withRunner((commandRunner, logger, updater, logs) async { 19 | final tempDirectory = Directory.systemTemp.createTempSync(); 20 | addTearDown(() => tempDirectory.deleteSync(recursive: true)); 21 | 22 | const projectName = 'my_dart_project'; 23 | await expectSuccessfulProcessResult( 24 | 'dart', 25 | ['create', 'my_dart_project', '--no-pub'], 26 | workingDirectory: tempDirectory.path, 27 | ); 28 | final projectPath = path.join(tempDirectory.path, projectName); 29 | await expectSuccessfulProcessResult( 30 | 'dart', 31 | ['pub', 'add', 'formz'], 32 | workingDirectory: projectPath, 33 | ); 34 | await expectSuccessfulProcessResult( 35 | 'dart', 36 | ['pub', 'get'], 37 | workingDirectory: projectPath, 38 | ); 39 | 40 | final relativeProjectPath = path.relative( 41 | projectPath, 42 | from: Directory.current.path, 43 | ); 44 | final resultAllowed = await commandRunner.run( 45 | [ 46 | 'packages', 47 | 'check', 48 | 'licenses', 49 | '--allowed=MIT', 50 | relativeProjectPath, 51 | ], 52 | ); 53 | expect( 54 | resultAllowed, 55 | equals(ExitCode.success.code), 56 | reason: 'Should succeed when allowed licenses are used', 57 | ); 58 | }), 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /e2e/test/commands/packages/check/licenses/licenses_forbidden_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:mason/mason.dart'; 4 | import 'package:path/path.dart' as path; 5 | import 'package:test/test.dart'; 6 | 7 | import '../../../../../helpers/helpers.dart'; 8 | 9 | /// Objectives: 10 | /// 11 | /// * Generate a new Dart project using (`dart create`) 12 | /// * Add dependencies to `pubspec.yaml` with an MIT license 13 | /// * Run `very_good packages check licenses --forbidden="MIT"` and expect 14 | /// failure 15 | void main() { 16 | test( 17 | 'packages check licenses --forbidden="MIT"', 18 | timeout: const Timeout(Duration(minutes: 2)), 19 | withRunner((commandRunner, logger, updater, logs) async { 20 | final tempDirectory = Directory.systemTemp.createTempSync(); 21 | addTearDown(() => tempDirectory.deleteSync(recursive: true)); 22 | 23 | const projectName = 'my_dart_project'; 24 | await expectSuccessfulProcessResult( 25 | 'dart', 26 | ['create', 'my_dart_project', '--no-pub'], 27 | workingDirectory: tempDirectory.path, 28 | ); 29 | final projectPath = path.join(tempDirectory.path, projectName); 30 | await expectSuccessfulProcessResult( 31 | 'dart', 32 | ['pub', 'add', 'formz'], 33 | workingDirectory: projectPath, 34 | ); 35 | await expectSuccessfulProcessResult( 36 | 'dart', 37 | ['pub', 'get'], 38 | workingDirectory: projectPath, 39 | ); 40 | 41 | final relativeProjectPath = path.relative( 42 | projectPath, 43 | from: Directory.current.path, 44 | ); 45 | 46 | final resultForbidden = await commandRunner.run( 47 | [ 48 | 'packages', 49 | 'check', 50 | 'licenses', 51 | '--forbidden=MIT', 52 | relativeProjectPath, 53 | ], 54 | ); 55 | expect( 56 | resultForbidden, 57 | equals(ExitCode.config.code), 58 | reason: 'Should fail when forbidden licenses are used', 59 | ); 60 | }), 61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /e2e/test/commands/test/async_main/async_main_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:mason/mason.dart'; 2 | import 'package:path/path.dart' as path; 3 | import 'package:test/test.dart'; 4 | import 'package:universal_io/io.dart'; 5 | 6 | import '../../../../helpers/helpers.dart'; 7 | 8 | void main() { 9 | test( 10 | 'supports async main methods', 11 | timeout: const Timeout(Duration(minutes: 2)), 12 | withRunner((commandRunner, logger, updater, logs) async { 13 | final tempDirectory = Directory.systemTemp.createTempSync('async_main'); 14 | addTearDown(() => tempDirectory.deleteSync(recursive: true)); 15 | 16 | final fixture = Directory( 17 | path.join( 18 | Directory.current.path, 19 | 'test/commands/test/async_main/fixture', 20 | ), 21 | ); 22 | 23 | await copyDirectory(fixture, tempDirectory); 24 | 25 | await expectSuccessfulProcessResult( 26 | 'flutter', 27 | ['pub', 'get'], 28 | workingDirectory: tempDirectory.path, 29 | ); 30 | 31 | final cwd = Directory.current; 32 | Directory.current = tempDirectory; 33 | addTearDown(() { 34 | Directory.current = cwd; 35 | }); 36 | 37 | final result = await commandRunner.run(['test']); 38 | expect(result, equals(ExitCode.success.code)); 39 | }), 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /e2e/test/commands/test/async_main/fixture/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: async_main 2 | description: Fixture for testing async main. 3 | version: 0.1.0+1 4 | publish_to: none 5 | 6 | environment: 7 | sdk: ^3.5.0 8 | 9 | dev_dependencies: 10 | test: ^1.24.3 11 | -------------------------------------------------------------------------------- /e2e/test/commands/test/async_main/fixture/test/async_main_test.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: prefer_const_constructors 2 | import 'package:test/test.dart'; 3 | 4 | void main() async { 5 | group('AsyncMain', () { 6 | test('will succeed', () { 7 | expect(true, equals(!false)); 8 | }); 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /e2e/test/commands/test/compilation_error/compilation_error_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:mason/mason.dart'; 2 | import 'package:mocktail/mocktail.dart'; 3 | import 'package:test/test.dart'; 4 | import 'package:universal_io/io.dart'; 5 | 6 | import '../../../../helpers/helpers.dart'; 7 | 8 | void main() { 9 | test( 10 | 'fails when there is a compilation error, but does not crash', 11 | timeout: const Timeout(Duration(minutes: 2)), 12 | withRunner((commandRunner, logger, updater, logs) async { 13 | final tempDirectory = 14 | Directory.systemTemp.createTempSync('compilation_error'); 15 | addTearDown(() => tempDirectory.deleteSync(recursive: true)); 16 | 17 | await copyDirectory( 18 | Directory('test/commands/test/compilation_error/fixture'), 19 | tempDirectory, 20 | ); 21 | 22 | await expectSuccessfulProcessResult( 23 | 'flutter', 24 | ['pub', 'get'], 25 | workingDirectory: tempDirectory.path, 26 | ); 27 | 28 | final cwd = Directory.current; 29 | Directory.current = tempDirectory; 30 | addTearDown(() { 31 | Directory.current = cwd; 32 | }); 33 | 34 | final result = await commandRunner.run(['test']); 35 | 36 | expect(result, equals(ExitCode.unavailable.code)); 37 | verify( 38 | () => logger.err(any(that: contains('- test/.test_optimizer.dart'))), 39 | ).called(1); 40 | }), 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /e2e/test/commands/test/compilation_error/fixture/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | exclude: 3 | - test 4 | -------------------------------------------------------------------------------- /e2e/test/commands/test/compilation_error/fixture/lib/compilation_error.dart: -------------------------------------------------------------------------------- 1 | class Thing { 2 | const Thing(); 3 | } 4 | -------------------------------------------------------------------------------- /e2e/test/commands/test/compilation_error/fixture/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: compilation_error 2 | version: 0.1.0+1 3 | publish_to: none 4 | 5 | environment: 6 | sdk: ^3.5.0 7 | 8 | dev_dependencies: 9 | test: ^1.24.3 10 | -------------------------------------------------------------------------------- /e2e/test/commands/test/compilation_error/fixture/test/src/my_package_test.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: prefer_const_constructors 2 | 3 | import 'package:compilation_error/compilation_error.dart'; 4 | import 'package:test/test.dart'; 5 | 6 | void main() { 7 | test('can be instantiated', () { 8 | expect(Thing(thing: true), isNull); 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /e2e/test/commands/test/no_project/fixture/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: no_project 2 | description: Fixture for testing no project. 3 | version: 0.1.0+1 4 | publish_to: none 5 | 6 | environment: 7 | sdk: ">=3.0.0 <4.0.0" 8 | 9 | dev_dependencies: 10 | test: ^1.24.3 11 | test_api: 0.6.0 12 | -------------------------------------------------------------------------------- /e2e/test/commands/test/no_project/no_project_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:mason/mason.dart'; 2 | import 'package:mocktail/mocktail.dart'; 3 | import 'package:test/test.dart'; 4 | import 'package:universal_io/io.dart'; 5 | 6 | import '../../../../helpers/helpers.dart'; 7 | 8 | void main() { 9 | test( 10 | 'fails if the project does not exist', 11 | withRunner((commandRunner, logger, updater, logs) async { 12 | final tempDirectory = Directory.systemTemp.createTempSync('no_project'); 13 | addTearDown(() => tempDirectory.deleteSync(recursive: true)); 14 | 15 | await copyDirectory( 16 | Directory('test/commands/test/no_project/fixture'), 17 | tempDirectory, 18 | ); 19 | 20 | await expectSuccessfulProcessResult( 21 | 'flutter', 22 | ['pub', 'get'], 23 | workingDirectory: tempDirectory.path, 24 | ); 25 | 26 | final cwd = Directory.current; 27 | Directory.current = tempDirectory; 28 | addTearDown(() { 29 | Directory.current = cwd; 30 | }); 31 | 32 | final result = await commandRunner.run(['test']); 33 | 34 | verifyNever(() => logger.err(any())); 35 | expect(result, equals(ExitCode.success.code)); 36 | }), 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /e2e/test/commands/test/spaced_golden_file_name/fixture/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: spaced_golden_file_name 2 | description: Fixture for testing golden files with spaced file names. 3 | publish_to: none 4 | 5 | environment: 6 | sdk: ">=3.0.0 <4.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | dev_dependencies: 13 | flutter_test: 14 | sdk: flutter 15 | -------------------------------------------------------------------------------- /e2e/test/commands/test/spaced_golden_file_name/fixture/test/sized box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VeryGoodOpenSource/very_good_cli/06daf69e0ac171d2ebdedeaccd815f9f680cd4cb/e2e/test/commands/test/spaced_golden_file_name/fixture/test/sized box.png -------------------------------------------------------------------------------- /e2e/test/commands/test/spaced_golden_file_name/fixture/test/spaced_golden_file_name_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | void main() { 5 | testWidgets('renders SizedBox', (tester) async { 6 | final widget = SizedBox.shrink(); 7 | await tester.pumpWidget(widget); 8 | 9 | await expectLater( 10 | find.byWidget(widget), 11 | matchesGoldenFile('sized box.png'), 12 | ); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /e2e/test/commands/test/spaced_golden_file_name/spaced_golden_file_name_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:mason/mason.dart'; 2 | import 'package:mocktail/mocktail.dart'; 3 | import 'package:test/test.dart'; 4 | import 'package:universal_io/io.dart'; 5 | 6 | import '../../../../helpers/helpers.dart'; 7 | 8 | void main() { 9 | test( 10 | 'allows golden files with spaces in the name', 11 | timeout: const Timeout(Duration(minutes: 2)), 12 | withRunner((commandRunner, logger, updater, logs) async { 13 | final tempDirectory = Directory.systemTemp.createTempSync('async_main'); 14 | addTearDown(() => tempDirectory.deleteSync(recursive: true)); 15 | 16 | await copyDirectory( 17 | Directory('test/commands/test/spaced_golden_file_name/fixture'), 18 | tempDirectory, 19 | ); 20 | 21 | await expectSuccessfulProcessResult( 22 | 'flutter', 23 | ['pub', 'get'], 24 | workingDirectory: tempDirectory.path, 25 | ); 26 | await expectSuccessfulProcessResult( 27 | 'flutter', 28 | ['test', '--update-goldens'], 29 | workingDirectory: tempDirectory.path, 30 | ); 31 | 32 | Directory.current = tempDirectory; 33 | final result = await commandRunner.run(['test']); 34 | 35 | verifyNever(() => logger.err(any())); 36 | expect(result, equals(ExitCode.success.code)); 37 | }), 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # Example 2 | 3 | ```sh 4 | # Activate Very Good CLI 5 | dart pub global activate very_good_cli 6 | 7 | # See list of available commands 8 | very_good --help 9 | ``` 10 | -------------------------------------------------------------------------------- /lib/src/cli/dart_cli.dart: -------------------------------------------------------------------------------- 1 | part of 'cli.dart'; 2 | 3 | /// Dart CLI 4 | class Dart { 5 | /// Determine whether dart is installed. 6 | static Future installed({ 7 | required Logger logger, 8 | }) async { 9 | try { 10 | await _Cmd.run('dart', ['--version'], logger: logger); 11 | return true; 12 | } catch (_) { 13 | return false; 14 | } 15 | } 16 | 17 | /// Install dart dependencies (`dart pub get`). 18 | static Future pubGet({ 19 | required Logger logger, 20 | String cwd = '.', 21 | bool recursive = false, 22 | Set ignore = const {}, 23 | }) async { 24 | final initialCwd = cwd; 25 | 26 | final result = await _runCommand( 27 | cmd: (cwd) async { 28 | final relativePath = p.relative(cwd, from: initialCwd); 29 | final path = 30 | relativePath == '.' ? '.' : '.${p.context.separator}$relativePath'; 31 | 32 | final installProgress = logger.progress( 33 | 'Running "dart pub get" in $path', 34 | ); 35 | 36 | try { 37 | await _verifyGitDependencies(cwd, logger: logger); 38 | } catch (_) { 39 | installProgress.fail(); 40 | rethrow; 41 | } 42 | 43 | try { 44 | return await _Cmd.run( 45 | 'dart', 46 | ['pub', 'get'], 47 | workingDirectory: cwd, 48 | logger: logger, 49 | ); 50 | } finally { 51 | installProgress.complete(); 52 | } 53 | }, 54 | cwd: cwd, 55 | recursive: recursive, 56 | ignore: ignore, 57 | ); 58 | return result.every((e) => e.exitCode == ExitCode.success.code); 59 | } 60 | 61 | /// Apply all fixes (`dart fix --apply`). 62 | static Future applyFixes({ 63 | required Logger logger, 64 | String cwd = '.', 65 | bool recursive = false, 66 | Set ignore = const {}, 67 | }) async { 68 | if (!recursive) { 69 | final pubspec = File(p.join(cwd, 'pubspec.yaml')); 70 | if (!pubspec.existsSync()) throw PubspecNotFound(); 71 | 72 | await _Cmd.run( 73 | 'dart', 74 | ['fix', '--apply'], 75 | workingDirectory: cwd, 76 | logger: logger, 77 | ); 78 | return; 79 | } 80 | 81 | final processes = _Cmd.runWhere( 82 | run: (entity) => _Cmd.run( 83 | 'dart', 84 | ['fix', '--apply'], 85 | workingDirectory: entity.parent.path, 86 | logger: logger, 87 | ), 88 | where: (entity) => !ignore.excludes(entity) && _isPubspec(entity), 89 | cwd: cwd, 90 | ); 91 | 92 | if (processes.isEmpty) throw PubspecNotFound(); 93 | 94 | await Future.wait(processes); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/src/cli/git_cli.dart: -------------------------------------------------------------------------------- 1 | part of 'cli.dart'; 2 | 3 | /// {@template unreachable_git_dependency} 4 | /// Thrown when `flutter pub get` encounters an unreachable git dependency. 5 | /// {@endtemplate} 6 | class UnreachableGitDependency implements Exception { 7 | /// {@macro unreachable_git_dependency} 8 | const UnreachableGitDependency({required this.remote}); 9 | 10 | /// The associated git remote [Uri]. 11 | final Uri remote; 12 | 13 | @override 14 | String toString() { 15 | return ''' 16 | $remote is unreachable. 17 | Make sure the remote exists and you have the correct access rights.'''; 18 | } 19 | } 20 | 21 | /// Git CLI 22 | class Git { 23 | /// Determine whether the [remote] is reachable. 24 | static Future reachable( 25 | Uri remote, { 26 | required Logger logger, 27 | }) async { 28 | try { 29 | await _Cmd.run( 30 | 'git', 31 | ['ls-remote', '$remote', '--exit-code'], 32 | logger: logger, 33 | ); 34 | } catch (_) { 35 | throw UnreachableGitDependency(remote: remote); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/src/commands/commands.dart: -------------------------------------------------------------------------------- 1 | export 'create/commands/commands.dart'; 2 | export 'create/create.dart'; 3 | export 'packages/packages.dart'; 4 | export 'test/test.dart'; 5 | export 'update.dart'; 6 | -------------------------------------------------------------------------------- /lib/src/commands/create/commands/commands.dart: -------------------------------------------------------------------------------- 1 | export 'create_subcommand.dart'; 2 | export 'dart_cli.dart'; 3 | export 'dart_package.dart'; 4 | export 'docs_site.dart'; 5 | export 'flame_game.dart'; 6 | export 'flutter_app.dart'; 7 | export 'flutter_package.dart'; 8 | export 'flutter_plugin.dart'; 9 | -------------------------------------------------------------------------------- /lib/src/commands/create/commands/dart_cli.dart: -------------------------------------------------------------------------------- 1 | import 'package:very_good_cli/src/commands/create/commands/commands.dart'; 2 | import 'package:very_good_cli/src/commands/create/templates/templates.dart'; 3 | 4 | /// {@template very_good_create_dart_cli_command} 5 | /// A [CreateSubCommand] for creating Dart command line interfaces. 6 | /// {@endtemplate} 7 | class CreateDartCLI extends CreateSubCommand with Publishable { 8 | /// {@macro very_good_create_dart_cli_command} 9 | CreateDartCLI({ 10 | required super.logger, 11 | required super.generatorFromBundle, 12 | required super.generatorFromBrick, 13 | }) { 14 | argParser.addOption( 15 | 'executable-name', 16 | help: 'The CLI executable name (defaults to the project name)', 17 | ); 18 | } 19 | 20 | @override 21 | String get name => 'dart_cli'; 22 | 23 | @override 24 | String get description => 'Generate a Very Good Dart CLI application.'; 25 | 26 | @override 27 | Template get template => VeryGoodDartCLITemplate(); 28 | 29 | @override 30 | Map getTemplateVars() { 31 | final vars = super.getTemplateVars(); 32 | 33 | final executableName = 34 | argResults['executable-name'] as String? ?? projectName; 35 | 36 | vars['executable_name'] = executableName; 37 | 38 | return vars; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/src/commands/create/commands/dart_package.dart: -------------------------------------------------------------------------------- 1 | import 'package:very_good_cli/src/commands/commands.dart'; 2 | import 'package:very_good_cli/src/commands/create/templates/templates.dart'; 3 | 4 | /// {@template very_good_create_dart_package_command} 5 | /// A [CreateSubCommand] for creating Dart packages. 6 | /// {@endtemplate} 7 | class CreateDartPackage extends CreateSubCommand with Publishable { 8 | /// {@macro very_good_create_dart_package_command} 9 | CreateDartPackage({ 10 | required super.logger, 11 | required super.generatorFromBundle, 12 | required super.generatorFromBrick, 13 | }); 14 | 15 | @override 16 | String get name => 'dart_package'; 17 | 18 | @override 19 | List get aliases => ['dart_pkg']; 20 | 21 | @override 22 | String get description => 'Generate a Very Good Dart package.'; 23 | 24 | @override 25 | Template get template => DartPkgTemplate(); 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/commands/create/commands/docs_site.dart: -------------------------------------------------------------------------------- 1 | import 'package:very_good_cli/src/commands/commands.dart'; 2 | import 'package:very_good_cli/src/commands/create/templates/templates.dart'; 3 | 4 | /// {@template very_good_create_docs_site} 5 | /// A [CreateSubCommand] for creating Dart command line interfaces. 6 | /// {@endtemplate} 7 | class CreateDocsSite extends CreateSubCommand { 8 | /// {@macro very_good_create_docs_site} 9 | CreateDocsSite({ 10 | required super.logger, 11 | required super.generatorFromBundle, 12 | required super.generatorFromBrick, 13 | }) { 14 | argParser.addOption( 15 | 'org-name', 16 | help: 'The organization for this new project.', 17 | defaultsTo: _defaultOrgName, 18 | aliases: ['org'], 19 | ); 20 | } 21 | 22 | static const _defaultOrgName = 'my-org'; 23 | 24 | @override 25 | String get name => 'docs_site'; 26 | 27 | @override 28 | String get description => 'Generate a Very Good documentation site.'; 29 | 30 | @override 31 | Map getTemplateVars() { 32 | return { 33 | ...super.getTemplateVars(), 34 | 'org_name': argResults['org-name'], 35 | }; 36 | } 37 | 38 | @override 39 | Template get template => VeryGoodDocsSiteTemplate(); 40 | } 41 | -------------------------------------------------------------------------------- /lib/src/commands/create/commands/flame_game.dart: -------------------------------------------------------------------------------- 1 | import 'package:very_good_cli/src/commands/create/commands/create_subcommand.dart'; 2 | import 'package:very_good_cli/src/commands/create/templates/templates.dart'; 3 | 4 | /// {@template very_good_create_flame_game_command} 5 | /// A [CreateSubCommand] for creating Flame games. 6 | /// {@endtemplate} 7 | class CreateFlameGame extends CreateSubCommand with OrgName { 8 | /// {@macro very_good_create_flame_game_command} 9 | CreateFlameGame({ 10 | required super.logger, 11 | required super.generatorFromBundle, 12 | required super.generatorFromBrick, 13 | }); 14 | 15 | @override 16 | String get name => 'flame_game'; 17 | 18 | @override 19 | String get description => 'Generate a Very Good Flame game.'; 20 | 21 | @override 22 | Template get template => VeryGoodFlameGameTemplate(); 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/commands/create/commands/flutter_app.dart: -------------------------------------------------------------------------------- 1 | import 'package:very_good_cli/src/commands/create/commands/create_subcommand.dart'; 2 | import 'package:very_good_cli/src/commands/create/templates/templates.dart'; 3 | 4 | /// {@template very_good_create_flutter_app_command} 5 | /// A [CreateSubCommand] for creating Flutter apps. 6 | /// {@endtemplate} 7 | class CreateFlutterApp extends CreateSubCommand with OrgName, MultiTemplates { 8 | /// {@macro very_good_create_flutter_app_command} 9 | CreateFlutterApp({ 10 | required super.logger, 11 | required super.generatorFromBundle, 12 | required super.generatorFromBrick, 13 | }) { 14 | argParser.addOption( 15 | 'application-id', 16 | help: 'The bundle identifier on iOS or application id on Android. ' 17 | '(defaults to .)', 18 | ); 19 | } 20 | 21 | @override 22 | String get name => 'flutter_app'; 23 | 24 | @override 25 | String get description => 'Generate a Very Good Flutter application.'; 26 | 27 | @override 28 | Map getTemplateVars() { 29 | final vars = super.getTemplateVars(); 30 | 31 | final applicationId = argResults['application-id'] as String?; 32 | if (applicationId != null) { 33 | vars['application_id'] = applicationId; 34 | } 35 | 36 | return vars; 37 | } 38 | 39 | @override 40 | final List