├── .editorconfig ├── .env.dist ├── .env.test ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── auto-merge.yaml │ ├── dispatch-files-branches.yaml │ ├── dispatch-labels-hooks-topics-settings.yaml │ ├── documentation.yaml │ ├── lint.yaml │ ├── qa.yaml │ ├── release.yaml │ └── test.yaml ├── .gitignore ├── .php-cs-fixer.dist.php ├── .php-version ├── .platform.app.yaml ├── .platform ├── local │ └── project.yaml ├── routes.yaml └── services.yaml ├── .readthedocs.yaml ├── .yamllint ├── CONTRIBUTING.md ├── Makefile ├── README.md ├── autoload.php ├── bin └── console ├── composer.json ├── composer.lock ├── config ├── bundles.php ├── labels.yaml ├── packages │ ├── cache.yaml │ ├── dev │ │ ├── debug.yaml │ │ ├── monolog.yaml │ │ └── web_profiler.yaml │ ├── framework.yaml │ ├── github_api.yaml │ ├── knp_packagist_api.yaml │ ├── monolog.yaml │ ├── notifier.yaml │ ├── nyholm_psr7.yaml │ ├── oskarstark_env_var_extension.yaml │ ├── prod │ │ ├── deprecations.yaml │ │ ├── monolog.yaml │ │ └── routing.yaml │ ├── routing.yaml │ ├── test │ │ ├── framework.yaml │ │ ├── monolog.yaml │ │ ├── twig.yaml │ │ └── web_profiler.yaml │ └── twig.yaml ├── preload.php ├── projects.yaml ├── routes.yaml ├── routes │ ├── annotations.yaml │ └── dev │ │ ├── framework.yaml │ │ └── web_profiler.yaml └── services.yaml ├── configure ├── docs ├── .doctor-rst.yaml ├── _static │ └── .gitignore ├── conf.py ├── index.rst ├── pages │ └── contribute.rst └── requirements.txt ├── php.ini ├── phpstan-baseline.neon ├── phpstan-console-application.php ├── phpstan.neon.dist ├── phpunit.xml.dist ├── psalm.xml ├── public ├── apple-touch-icon.png ├── favicon.png └── index.php ├── rector.php ├── src ├── Action │ ├── DetermineNextRelease.php │ ├── DetermineNextReleaseVersion.php │ └── Exception │ │ └── NoPullRequestsMergedSinceLastRelease.php ├── Command │ ├── AbstractCommand.php │ ├── AbstractNeedApplyCommand.php │ ├── AutoMergeCommand.php │ ├── CommentNonMergeablePullRequestsCommand.php │ ├── DependsCommand.php │ ├── Dispatcher │ │ ├── DispatchBranchesProtectionCommand.php │ │ ├── DispatchFilesCommand.php │ │ ├── DispatchHooksCommand.php │ │ ├── DispatchLabelsCommand.php │ │ ├── DispatchSettingsCommand.php │ │ └── DispatchTopicsCommand.php │ └── ReleaseCommand.php ├── Config │ ├── ConfiguredLabels.php │ ├── Exception │ │ ├── UnknownBranch.php │ │ └── UnknownProject.php │ ├── LabelsConfiguration.php │ ├── Projects.php │ └── ProjectsConfiguration.php ├── Controller │ ├── DefaultController.php │ ├── GithubController.php │ ├── NextReleaseForProjectController.php │ └── NextReleaseOverviewController.php ├── Domain │ ├── Exception │ │ ├── CannotGenerateChangelog.php │ │ └── NoBranchesAvailable.php │ └── Value │ │ ├── Branch.php │ │ ├── Changelog.php │ │ ├── Changelog │ │ └── Section.php │ │ ├── ExcludedFile.php │ │ ├── NextRelease.php │ │ ├── Path.php │ │ ├── PhpExtension.php │ │ ├── PhpVersion.php │ │ ├── Project.php │ │ ├── Repository.php │ │ ├── Stability.php │ │ ├── TrimmedNonEmptyString.php │ │ └── Variant.php ├── Git │ └── GitManipulator.php ├── Github │ ├── Api │ │ ├── BranchProtections.php │ │ ├── Branches.php │ │ ├── Checks.php │ │ ├── Comments.php │ │ ├── Commits.php │ │ ├── Hooks.php │ │ ├── Issues.php │ │ ├── Labels.php │ │ ├── PullRequests.php │ │ ├── Releases.php │ │ ├── Repositories.php │ │ ├── Statuses.php │ │ └── Topics.php │ ├── Domain │ │ └── Value │ │ │ ├── Branch.php │ │ │ ├── CheckRun.php │ │ │ ├── CheckRuns.php │ │ │ ├── CombinedStatus.php │ │ │ ├── Comment.php │ │ │ ├── Commit.php │ │ │ ├── Hook.php │ │ │ ├── Hook │ │ │ ├── Config.php │ │ │ └── Events.php │ │ │ ├── IncomingWebhook │ │ │ ├── Action.php │ │ │ ├── Event.php │ │ │ └── Payload.php │ │ │ ├── Issue.php │ │ │ ├── Label.php │ │ │ ├── Label │ │ │ └── Color.php │ │ │ ├── PullRequest.php │ │ │ ├── PullRequest │ │ │ ├── Base.php │ │ │ ├── Head.php │ │ │ └── Head │ │ │ │ └── Repo.php │ │ │ ├── Release.php │ │ │ ├── Release │ │ │ └── Tag.php │ │ │ ├── Repository.php │ │ │ ├── Search │ │ │ └── Query.php │ │ │ ├── Sha.php │ │ │ ├── Status.php │ │ │ ├── Url.php │ │ │ └── User.php │ ├── Exception │ │ └── LatestReleaseNotFound.php │ └── HookProcessor.php ├── Kernel.php └── Twig │ └── Extension │ ├── FileSystemExtension.php │ └── GithubSponsorsExtension.php ├── symfony.lock ├── templates ├── base.html.twig ├── default │ └── index.html.twig ├── github │ ├── bundles-todo-list.markdown │ ├── help.markdown │ └── request-release.markdown ├── macros.html.twig ├── nav_and_content.html.twig ├── project │ ├── .babelrc.js │ ├── .editorconfig │ ├── .eslintrc.js │ ├── .gitattributes.twig │ ├── .github │ │ ├── FUNDING.yml.twig │ │ ├── ISSUE_TEMPLATE │ │ │ ├── Bug.md.twig │ │ │ ├── Feature.md.twig │ │ │ └── config.yml │ │ ├── PULL_REQUEST_TEMPLATE.md.twig │ │ └── workflows │ │ │ ├── auto-merge-dev-kit.yaml.twig │ │ │ ├── documentation.yaml.twig │ │ │ ├── frontend.yaml.twig │ │ │ ├── lint.yaml.twig │ │ │ ├── qa.yaml.twig │ │ │ ├── stale.yaml │ │ │ ├── symfony-lint.yaml.twig │ │ │ ├── test-platforms.yaml.twig │ │ │ └── test.yaml.twig │ ├── .gitignore.twig │ ├── .php-cs-fixer.dist.php │ ├── .prettierignore │ ├── .readthedocs.yaml │ ├── .stylelintrc.js │ ├── .symfony.bundle.yaml.twig │ ├── .yamllint │ ├── CONTRIBUTING.md.twig │ ├── LICENSE │ ├── Makefile.twig │ ├── README.md.twig │ ├── bin │ │ └── console.twig │ ├── docs │ │ ├── .doctor-rst.yaml.twig │ │ ├── conf.py.twig │ │ └── requirements.txt │ ├── phpstan.neon.dist.twig │ ├── phpunit.xml.dist.twig │ ├── postcss.config.js │ ├── prettier.config.js │ ├── rector.php.twig │ └── tests │ │ └── bootstrap.php.twig └── releases │ ├── overview.html.twig │ └── project.html.twig └── tests ├── Action ├── DetermineNextReleaseVersionTest.php └── Exception │ └── NoPullRequestMergedSinceLastReleaseTest.php ├── Command └── Dispatcher │ └── DispatchFilesCommandTest.php ├── Config └── Exception │ ├── UnknownBranchTest.php │ └── UnknownProjectTest.php ├── Domain ├── Exception │ └── NoBranchesAvaillableTest.php └── Value │ ├── BranchTest.php │ ├── ProjectTest.php │ ├── RepositoryTest.php │ ├── StabilityTest.php │ ├── TrimmedNonEmptyStringTest.php │ └── VariantTest.php ├── Github ├── Domain │ └── Value │ │ ├── CheckRunsTest.php │ │ ├── CombinedStatusTest.php │ │ ├── CommitTest.php │ │ ├── HookTest.php │ │ ├── IncomingWebhook │ │ ├── ActionTest.php │ │ ├── EventTest.php │ │ └── PayloadTest.php │ │ ├── Issue │ │ └── IssueTest.php │ │ ├── Label │ │ └── ColorTest.php │ │ ├── LabelTest.php │ │ ├── PullRequest │ │ ├── Head │ │ │ └── RepoTest.php │ │ └── HeadTest.php │ │ ├── PullRequestTest.php │ │ ├── Release │ │ └── TagTest.php │ │ ├── RepositoryTest.php │ │ ├── Search │ │ └── QueryTest.php │ │ ├── ShaTest.php │ │ ├── StatusTest.php │ │ ├── UrlTest.php │ │ └── UserTest.php └── Exception │ └── LatestReleaseNotFoundTest.php ├── Twig └── Extension │ ├── FileSystemExtensionTest.php │ └── GithubSponsorsExtensionTest.php ├── Util ├── DataProvider │ └── StringProvider.php └── Factory │ └── Github │ └── Response │ ├── CheckRunFactory.php │ ├── CheckRunsFactory.php │ ├── CombinedStatusFactory.php │ ├── LabelFactory.php │ ├── PullRequestFactory.php │ ├── StatusFactory.php │ └── UserFactory.php └── bootstrap.php /.editorconfig: -------------------------------------------------------------------------------- 1 | templates/project/.editorconfig -------------------------------------------------------------------------------- /.env.dist: -------------------------------------------------------------------------------- 1 | GITHUB_OAUTH_TOKEN=token_to_be_changed 2 | DEV_KIT_TOKEN=token_to_be_changed 3 | SLACK_TOKEN=token_to_be_changed 4 | 5 | ###> symfony/framework-bundle ### 6 | APP_ENV=dev 7 | APP_SECRET=de84d932c6c720a3288f0737afa7d190 8 | ###< symfony/framework-bundle ### 9 | 10 | ###> symfony/slack-notifier ### 11 | # See https://api.slack.com/messaging/webhooks 12 | SLACK_DSN=slack://token_to_be_changed@default/T0M3KPUH1/B01ECH8AVAM/qromNSIvTexlZ0EAlHjKEU5j 13 | ###< symfony/slack-notifier ### 14 | 15 | # 127.0.0.1:6379 are the default host and port for Redis, change it if needed. 16 | REDIS_HOST=127.0.0.1 17 | REDIS_PORT=6379 18 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | # define your env variables for the test env here 2 | KERNEL_CLASS='App\Kernel' 3 | APP_SECRET='$ecretf0rt3st' 4 | SYMFONY_DEPRECATIONS_HELPER=999999 5 | PANTHER_APP_ENV=panther 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [OskarStark, VincentLanglet, jordisala1991, greg0ire] 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://help.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#target-branch 2 | 3 | version: 2 4 | 5 | updates: 6 | - 7 | allow: 8 | - dependency-type: "direct" 9 | directory: "/" 10 | labels: 11 | - "dependencies" 12 | - "automerge" 13 | open-pull-requests-limit: 5 14 | package-ecosystem: composer 15 | schedule: 16 | interval: daily 17 | versioning-strategy: "lockfile-only" 18 | 19 | # Maintain dependencies for GitHub Actions 20 | - 21 | package-ecosystem: "github-actions" 22 | labels: 23 | - "dependencies" 24 | - "automerge" 25 | directory: "/" 26 | schedule: 27 | interval: "daily" 28 | -------------------------------------------------------------------------------- /.github/workflows/auto-merge.yaml: -------------------------------------------------------------------------------- 1 | name: Auto merge 2 | 3 | on: 4 | schedule: 5 | # At 02:00 6 | # https://crontab.guru/#0_2_*_*_* 7 | - cron: '0 2 * * *' 8 | workflow_dispatch: 9 | inputs: 10 | projects: 11 | description: 'Projects to do an auto merge, separated by spaces (empty means all)' 12 | 13 | env: 14 | REQUIRED_PHP_EXTENSIONS: redis 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | dispatch: 21 | name: PHP ${{ matrix.php-version }} 22 | 23 | runs-on: ubuntu-latest 24 | 25 | strategy: 26 | matrix: 27 | php-version: 28 | - '8.2' 29 | 30 | services: 31 | redis: 32 | image: redis 33 | options: >- 34 | --health-cmd "redis-cli ping" 35 | --health-interval 10s 36 | --health-timeout 5s 37 | --health-retries 5 38 | ports: 39 | - 6379:6379 40 | 41 | env: 42 | APP_ENV: prod 43 | APP_DEBUG: 0 44 | REDIS_HOST: localhost 45 | REDIS_PORT: 6379 46 | GITHUB_OAUTH_TOKEN: ${{ secrets.OAUTH_TOKEN_GITHUB }} 47 | DEV_KIT_TOKEN: ${{ secrets.DEV_KIT_TOKEN }} 48 | SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} 49 | 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v4 53 | 54 | - name: Install PHP with extensions 55 | uses: shivammathur/setup-php@v2 56 | with: 57 | php-version: ${{ matrix.php-version }} 58 | extensions: ${{ env.REQUIRED_PHP_EXTENSIONS }} 59 | coverage: none 60 | tools: composer:v2 61 | 62 | - name: Install Composer dependencies (locked) 63 | uses: ramsey/composer-install@v3 64 | with: 65 | composer-options: --no-dev --classmap-authoritative 66 | 67 | - name: Dump env 68 | run: composer dump-env prod 69 | 70 | - name: Dispatch files 71 | run: bin/console auto-merge ${{ github.event.inputs.projects }} --apply 72 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yaml: -------------------------------------------------------------------------------- 1 | # DO NOT EDIT THIS FILE! 2 | # 3 | # It's auto-generated by sonata-project/dev-kit package. 4 | 5 | name: Documentation 6 | 7 | on: 8 | push: 9 | branches: 10 | - master 11 | pull_request: 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | build: 18 | name: Sphinx build 19 | 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | 26 | - name: Set up Python 3.11 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: '3.11' 30 | cache: 'pip' 31 | 32 | - name: Install custom requirements via pip 33 | run: pip install -r docs/requirements.txt 34 | 35 | - name: Build documentation 36 | run: make docs 37 | 38 | doctor-rst: 39 | name: DOCtor-RST 40 | 41 | runs-on: ubuntu-latest 42 | 43 | steps: 44 | - name: Checkout 45 | uses: actions/checkout@v4 46 | 47 | - name: Run DOCtor-RST 48 | uses: docker://oskarstark/doctor-rst 49 | with: 50 | args: --short --error-format=github 51 | env: 52 | DOCS_DIR: docs/ 53 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | php-cs-fixer: 14 | name: PHP-CS-Fixer 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Install PHP with extensions 23 | uses: shivammathur/setup-php@v2 24 | with: 25 | php-version: '8.2' 26 | coverage: none 27 | tools: composer:v2 28 | 29 | - name: Install Composer dependencies (locked) 30 | uses: ramsey/composer-install@v3 31 | 32 | - name: Lint PHP files 33 | run: make lint-php 34 | 35 | composer-normalize: 36 | name: composer-normalize 37 | 38 | runs-on: ubuntu-latest 39 | 40 | steps: 41 | - name: Checkout 42 | uses: actions/checkout@v4 43 | 44 | - name: Install PHP with extensions 45 | uses: shivammathur/setup-php@v2 46 | with: 47 | php-version: '8.2' 48 | coverage: none 49 | tools: composer:v2, composer-normalize:2 50 | env: 51 | COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | 53 | - name: Lint Composer 54 | run: make lint-composer 55 | 56 | yaml-files: 57 | name: YAML files 58 | 59 | runs-on: ubuntu-latest 60 | 61 | steps: 62 | - name: Checkout 63 | uses: actions/checkout@v4 64 | 65 | - name: Install yamllint 66 | run: sudo apt-get install yamllint 67 | 68 | - name: Lint files 69 | run: make lint-yaml 70 | 71 | xml-files: 72 | name: XML files 73 | 74 | runs-on: ubuntu-latest 75 | 76 | steps: 77 | - name: Checkout 78 | uses: actions/checkout@v4 79 | 80 | - name: Install required dependencies 81 | run: sudo apt-get update && sudo apt-get install libxml2-utils 82 | 83 | - name: Lint xml files 84 | run: make lint-xml 85 | 86 | - name: Lint xliff files 87 | run: make lint-xliff 88 | -------------------------------------------------------------------------------- /.github/workflows/qa.yaml: -------------------------------------------------------------------------------- 1 | name: Quality assurance 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | phpstan: 14 | name: PHPStan 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Install PHP with extensions 23 | uses: shivammathur/setup-php@v2 24 | with: 25 | php-version: '8.2' 26 | coverage: none 27 | tools: composer:v2 28 | 29 | - name: Install Composer dependencies (locked) 30 | uses: ramsey/composer-install@v3 31 | 32 | - name: PHPStan 33 | run: vendor/bin/phpstan --no-progress --memory-limit=1G analyse --error-format=github 34 | 35 | psalm: 36 | name: Psalm 37 | 38 | runs-on: ubuntu-latest 39 | 40 | steps: 41 | - name: Checkout code 42 | uses: actions/checkout@v4 43 | 44 | - name: Install PHP with extensions 45 | uses: shivammathur/setup-php@v2 46 | with: 47 | php-version: '8.2' 48 | coverage: none 49 | tools: composer:v2 50 | 51 | - name: Install Composer dependencies (locked) 52 | uses: ramsey/composer-install@v3 53 | 54 | - name: Psalm 55 | run: vendor/bin/psalm --no-progress --show-info=false --stats --output-format=github --threads=$(nproc) --shepherd --php-version=8.0 56 | 57 | rector: 58 | name: Rector 59 | 60 | runs-on: ubuntu-latest 61 | 62 | steps: 63 | - name: Checkout code 64 | uses: actions/checkout@v4 65 | 66 | - name: Install PHP with extensions 67 | uses: shivammathur/setup-php@v2 68 | with: 69 | php-version: '8.2' 70 | coverage: none 71 | tools: composer:v2 72 | 73 | - name: Install Composer dependencies (locked) 74 | uses: ramsey/composer-install@v3 75 | 76 | - name: Rector 77 | run: vendor/bin/rector --no-progress-bar --dry-run 78 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | project: 7 | required: true 8 | description: 'Project to release' 9 | branch: 10 | required: true 11 | description: 'Branch to release' 12 | 13 | env: 14 | REQUIRED_PHP_EXTENSIONS: redis 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | dispatch: 21 | name: PHP ${{ matrix.php-version }} 22 | 23 | runs-on: ubuntu-latest 24 | 25 | strategy: 26 | matrix: 27 | php-version: 28 | - '8.2' 29 | 30 | services: 31 | redis: 32 | image: redis 33 | options: >- 34 | --health-cmd "redis-cli ping" 35 | --health-interval 10s 36 | --health-timeout 5s 37 | --health-retries 5 38 | ports: 39 | - 6379:6379 40 | 41 | env: 42 | APP_ENV: prod 43 | APP_DEBUG: 0 44 | REDIS_HOST: localhost 45 | REDIS_PORT: 6379 46 | GITHUB_OAUTH_TOKEN: ${{ secrets.OAUTH_TOKEN_GITHUB }} 47 | DEV_KIT_TOKEN: ${{ secrets.DEV_KIT_TOKEN }} 48 | SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }} 49 | 50 | steps: 51 | - name: Checkout 52 | uses: actions/checkout@v4 53 | 54 | - name: Install PHP with extensions 55 | uses: shivammathur/setup-php@v2 56 | with: 57 | php-version: ${{ matrix.php-version }} 58 | extensions: ${{ env.REQUIRED_PHP_EXTENSIONS }} 59 | coverage: none 60 | tools: composer:v2 61 | 62 | - name: Install Composer dependencies (locked) 63 | uses: ramsey/composer-install@v3 64 | with: 65 | composer-options: --no-dev --classmap-authoritative 66 | 67 | - name: Dump env 68 | run: composer dump-env prod 69 | 70 | - name: Release 71 | run: bin/console release ${{ github.event.inputs.project }} ${{ github.event.inputs.branch }} --pr 72 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | env: 10 | REQUIRED_PHP_EXTENSIONS: redis 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | test: 17 | name: PHP ${{ matrix.php-version }} 18 | 19 | runs-on: ubuntu-latest 20 | 21 | strategy: 22 | matrix: 23 | php-version: 24 | - '8.1' 25 | - '8.2' 26 | 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v4 30 | 31 | - name: Install PHP with extensions 32 | uses: shivammathur/setup-php@v2 33 | with: 34 | php-version: ${{ matrix.php-version }} 35 | extensions: ${{ env.REQUIRED_PHP_EXTENSIONS }} 36 | coverage: pcov 37 | tools: composer:v2 38 | 39 | - name: Add PHPUnit matcher 40 | run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" 41 | 42 | - name: Install Composer dependencies (locked) 43 | uses: ramsey/composer-install@v3 44 | 45 | - name: Run Tests with coverage 46 | run: make coverage 47 | 48 | - name: Send coverage to Codecov 49 | uses: codecov/codecov-action@v5 50 | with: 51 | files: build/logs/clover.xml 52 | 53 | twig: 54 | name: Check Twig syntax 55 | 56 | runs-on: ubuntu-latest 57 | 58 | steps: 59 | - name: Checkout 60 | uses: actions/checkout@v4 61 | 62 | - name: Install PHP with extensions 63 | uses: shivammathur/setup-php@v2 64 | with: 65 | php-version: '8.2' 66 | extensions: ${{ env.REQUIRED_PHP_EXTENSIONS }} 67 | coverage: pcov 68 | tools: composer:v2 69 | 70 | - name: Install Composer dependencies (locked) 71 | uses: ramsey/composer-install@v3 72 | 73 | - name: Validate Twig syntax 74 | run: bin/console lint:twig templates/ 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.php-cs-fixer.cache 2 | /.env 3 | /.bundle 4 | /docs/_build/ 5 | 6 | ###> symfony/framework-bundle ### 7 | /.env.local 8 | /.env.local.php 9 | /.env.*.local 10 | /config/secrets/prod/prod.decrypt.private.php 11 | /public/bundles/ 12 | /var/ 13 | /vendor/ 14 | ###< symfony/framework-bundle ### 15 | 16 | ###> symfony/phpunit-bridge ### 17 | .phpunit 18 | .phpunit.result.cache 19 | /phpunit.xml 20 | ###< symfony/phpunit-bridge ### 21 | 22 | ###> friendsofphp/php-cs-fixer ### 23 | /.php-cs-fixer.php 24 | /.php-cs-fixer.cache 25 | ###< friendsofphp/php-cs-fixer ### 26 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | $config = include 'templates/project/.php-cs-fixer.dist.php'; 15 | 16 | $finder->in(__DIR__); 17 | 18 | return $config->setFinder($finder); 19 | -------------------------------------------------------------------------------- /.php-version: -------------------------------------------------------------------------------- 1 | 8.1 2 | -------------------------------------------------------------------------------- /.platform.app.yaml: -------------------------------------------------------------------------------- 1 | name: app 2 | 3 | type: php:8.1 4 | 5 | runtime: 6 | extensions: 7 | - mbstring 8 | - sodium 9 | - ctype 10 | - iconv 11 | - redis 12 | 13 | variables: 14 | php: 15 | opcache.preload: /app/config/preload.php 16 | 17 | relationships: 18 | redis: "github:redis" 19 | 20 | build: 21 | flavor: none 22 | 23 | dependencies: 24 | php: 25 | composer/composer: "^2" 26 | 27 | web: 28 | locations: 29 | "/": 30 | root: "public" 31 | expires: 1h 32 | passthru: "/index.php" 33 | 34 | disk: 512 35 | 36 | mounts: 37 | "/var": {source: local, source_path: var} 38 | 39 | hooks: 40 | build: | 41 | set -x -e 42 | 43 | curl -fs https://get.symfony.com/cloud/configurator | (>&2 bash) 44 | 45 | (>&2 46 | php-ext-install redis 5.3.2 47 | symfony-build 48 | ) 49 | 50 | deploy: | 51 | set -x -e 52 | 53 | (>&2 symfony-deploy) 54 | 55 | crons: 56 | comment-non-mergeable-pull-requests: 57 | spec: '*/10 * * * *' 58 | cmd: | 59 | if [ "$PLATFORM_BRANCH" = master ]; then 60 | croncape bin/console comment-non-mergeable-pull-requests --apply 61 | fi 62 | -------------------------------------------------------------------------------- /.platform/local/project.yaml: -------------------------------------------------------------------------------- 1 | id: ptm4dx6rjpjko 2 | host: eu-5.platform.sh 3 | -------------------------------------------------------------------------------- /.platform/routes.yaml: -------------------------------------------------------------------------------- 1 | "https://{all}/": {type: upstream, upstream: "app:http"} 2 | "http://{all}/": {type: redirect, to: "https://{all}/"} 3 | -------------------------------------------------------------------------------- /.platform/services.yaml: -------------------------------------------------------------------------------- 1 | github: 2 | # supported versions: 3.2, 4.0, 5.0, 6.0 3 | # 2.8 and 3.0 are also available but not maintained upstream 4 | type: redis:6.0 5 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | templates/project/.readthedocs.yaml -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | templates/project/.yamllint -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # DO NOT EDIT THIS FILE! 2 | # 3 | # It's auto-generated by sonata-project/dev-kit package. 4 | 5 | all: 6 | @echo "Please choose a task." 7 | .PHONY: all 8 | 9 | lint: lint-composer lint-yaml lint-xml lint-xliff lint-php 10 | .PHONY: lint 11 | 12 | lint-composer: 13 | composer-normalize --dry-run 14 | composer validate 15 | .PHONY: lint-composer 16 | 17 | lint-yaml: 18 | yamllint . 19 | 20 | .PHONY: lint-yaml 21 | 22 | lint-xml: 23 | find . -name '*.xml' \ 24 | -not -path './vendor/*' \ 25 | -not -path './src/Resources/public/vendor/*' \ 26 | | while read xmlFile; \ 27 | do \ 28 | XMLLINT_INDENT=' ' xmllint --encode UTF-8 --format "$$xmlFile"|diff - "$$xmlFile"; \ 29 | if [ $$? -ne 0 ] ;then exit 1; fi; \ 30 | done 31 | 32 | .PHONY: lint-xml 33 | 34 | lint-xliff: 35 | find . -name '*.xliff' \ 36 | -not -path './vendor/*' \ 37 | -not -path './src/Resources/public/vendor/*' \ 38 | | while read xmlFile; \ 39 | do \ 40 | XMLLINT_INDENT=' ' xmllint --encode UTF-8 --format "$$xmlFile"|diff - "$$xmlFile"; \ 41 | if [ $$? -ne 0 ] ;then exit 1; fi; \ 42 | done 43 | 44 | .PHONY: lint-xliff 45 | 46 | lint-php: 47 | vendor/bin/php-cs-fixer fix --ansi --verbose --diff --dry-run 48 | .PHONY: lint-php 49 | 50 | cs-fix: cs-fix-php cs-fix-xml cs-fixer-composer 51 | .PHONY: cs-fix 52 | 53 | cs-fix-php: 54 | vendor/bin/php-cs-fixer fix --verbose 55 | .PHONY: cs-fix-php 56 | 57 | cs-fix-xml: 58 | find . \( -name '*.xml' -or -name '*.xliff' \) \ 59 | -not -path './vendor/*' \ 60 | -not -path './src/Resources/public/vendor/*' \ 61 | | while read xmlFile; \ 62 | do \ 63 | XMLLINT_INDENT=' ' xmllint --encode UTF-8 --format "$$xmlFile" --output "$$xmlFile"; \ 64 | done 65 | .PHONY: cs-fix-xml 66 | 67 | cs-fix-composer: 68 | composer-normalize 69 | .PHONY: cs-fix-composer 70 | 71 | test: 72 | vendor/bin/phpunit -c phpunit.xml.dist 73 | .PHONY: test 74 | 75 | coverage: 76 | vendor/bin/phpunit -c phpunit.xml.dist --coverage-clover build/logs/clover.xml 77 | .PHONY: coverage 78 | 79 | docs: 80 | cd docs && sphinx-build -W -b dirhtml -d _build/doctrees . _build/html 81 | .PHONY: docs 82 | 83 | phpstan: 84 | vendor/bin/phpstan --memory-limit=1G analyse 85 | .PHONY: phpstan 86 | 87 | psalm: 88 | vendor/bin/psalm --php-version=8.2 89 | .PHONY: psalm 90 | 91 | rector: 92 | vendor/bin/rector 93 | .PHONY: rector 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sonata project development kit 2 | 3 | This repository contains all common documentation and tools for all Sonata projects. 4 | 5 | This one **must** be the **only** reference for how to contribute on those projects. 6 | 7 | Base URL: https://master-7rqtwti-ptm4dx6rjpjko.eu-5.platformsh.site/ 8 | 9 | ## Installation 10 | 11 | ```bash 12 | git clone git@github.com:sonata-project/dev-kit.git 13 | composer install 14 | symfony serve 15 | ``` 16 | And you can now access locally to dev-kit at http://127.0.0.1:8000/. 17 | 18 | Many variables in `.env` are tokens you will need to change. 19 | - `GITHUB_OAUTH_TOKEN` can be obtained at https://github.com/settings/tokens. 20 | - `DEV_KIT_TOKEN` 21 | - `SLACK_TOKEN` can be obtained at https://api.slack.com/apps/A018X725S0K/install-on-team. 22 | 23 | If your symfony connect account was added to the symfony cloud server, you can 24 | - Deploy the master branch with `symfony deploy`. 25 | - Access to the server by ssh (in order to run commands) with `symfony ssh`. 26 | 27 | This project implements many useful commands, you'll may need: 28 | - `bin/console auto-merge`: Merges branches of repositories if there is no conflict. 29 | - `bin/console release`: Helps with a project release. 30 | - `bin/console dispatch:files`: Dispatches files for all sonata projects. 31 | 32 | For the list of all commands run `bin/console list`. 33 | -------------------------------------------------------------------------------- /autoload.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | require_once __DIR__.'/vendor/autoload.php'; 15 | 16 | $dotenv = new Dotenv\Dotenv(__DIR__); 17 | $dotenv->load(); 18 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getParameterOption(['--env', '-e'], null, true)) { 24 | putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env); 25 | } 26 | 27 | if ($input->hasParameterOption('--no-debug', true)) { 28 | putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0'); 29 | } 30 | 31 | (new Dotenv())->bootEnv(dirname(__DIR__).'/.env'); 32 | 33 | if ($_SERVER['APP_DEBUG']) { 34 | umask(0000); 35 | 36 | if (class_exists(Debug::class)) { 37 | Debug::enable(); 38 | } 39 | } 40 | 41 | $kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); 42 | $application = new Application($kernel); 43 | $application->run($input); 44 | -------------------------------------------------------------------------------- /config/bundles.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | return [ 15 | Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true], 16 | Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true], 17 | Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], 18 | Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], 19 | Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], 20 | Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], 21 | ]; 22 | -------------------------------------------------------------------------------- /config/labels.yaml: -------------------------------------------------------------------------------- 1 | labels: 2 | - name: patch 3 | color: '207de5' 4 | - name: minor 5 | color: '009800' 6 | - name: major 7 | color: 'e11d21' 8 | - name: pedantic 9 | color: 'd1e4fa' 10 | - name: enhancement 11 | color: '02d7e1' 12 | - name: feature 13 | color: '02e10c' 14 | - name: bug 15 | color: 'fc2929' 16 | - name: unconfirmed 17 | color: '444444' 18 | - name: critical 19 | color: 'fc2929' 20 | - name: vendor 21 | color: 'ededed' 22 | - name: pending author 23 | color: 'ededed' 24 | - name: stale 25 | color: 'ededed' 26 | - name: in progress 27 | color: 'ededed' 28 | - name: RTM 29 | color: 'ffffff' 30 | - name: docs 31 | color: 'fbca04' 32 | - name: help wanted 33 | color: '0e8a16' 34 | - name: Easy Pick 35 | color: 'd7e102' 36 | - name: 'Needs: Documentation' 37 | color: '006b75' 38 | - name: 'Needs: Tests' 39 | color: '006b75' 40 | - name: 'Needs: Changelog note' 41 | color: '006b75' 42 | - name: 'Needs: Upgrade note' 43 | color: '006b75' 44 | - name: 'hacktoberfest' 45 | color: 'ff6600' 46 | - name: 'keep' 47 | color: '59b3c1' 48 | - name: 'dependencies' 49 | color: '0366d6' 50 | - name: 'automerge' 51 | color: '6dfd06' 52 | - name: 'dev-kit' 53 | color: '930bcf' 54 | -------------------------------------------------------------------------------- /config/packages/cache.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | cache: 3 | # Unique name of your app: used to compute stable namespaces for cache keys. 4 | #prefix_seed: your_vendor_name/app_name 5 | 6 | # The "app" cache stores to the filesystem by default. 7 | # The data in this cache should persist between deploys. 8 | # Other options include: 9 | 10 | # Redis 11 | #app: cache.adapter.redis 12 | #default_redis_provider: redis://localhost 13 | 14 | # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) 15 | #app: cache.adapter.apcu 16 | 17 | # Namespaced pools use the above "app" backend by default 18 | #pools: 19 | #my.dedicated.cache: null 20 | -------------------------------------------------------------------------------- /config/packages/dev/debug.yaml: -------------------------------------------------------------------------------- 1 | debug: 2 | # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser. 3 | # See the "server:dump" command to start a new server. 4 | dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%" 5 | -------------------------------------------------------------------------------- /config/packages/dev/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: stream 5 | path: "%kernel.logs_dir%/%kernel.environment%.log" 6 | level: debug 7 | channels: ["!event"] 8 | console: 9 | type: console 10 | process_psr_3_messages: false 11 | channels: ["!event", "!doctrine", "!console"] 12 | -------------------------------------------------------------------------------- /config/packages/dev/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: true 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: 7 | only_exceptions: false 8 | -------------------------------------------------------------------------------- /config/packages/framework.yaml: -------------------------------------------------------------------------------- 1 | # see https://symfony.com/doc/current/reference/configuration/framework.html 2 | framework: 3 | secret: '%env(APP_SECRET)%' 4 | #csrf_protection: true 5 | #http_method_override: true 6 | 7 | # Enables session support. Note that the session will ONLY be started if you read or write from it. 8 | # Remove or comment this section to explicitly disable session support. 9 | session: 10 | handler_id: null 11 | cookie_secure: auto 12 | cookie_samesite: lax 13 | 14 | #esi: true 15 | #fragments: true 16 | php_errors: 17 | log: true 18 | -------------------------------------------------------------------------------- /config/packages/github_api.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | Redis: 3 | class: Redis 4 | calls: 5 | - ['connect', ['%env(REDIS_HOST)%', '%env(int:REDIS_PORT)%']] 6 | 7 | Symfony\Component\Cache\Adapter\RedisAdapter: 8 | arguments: 9 | - '@Redis' 10 | 11 | Github\Client: 12 | arguments: 13 | - '@Github\HttpClient\Builder' 14 | calls: 15 | - ['authenticate', ['%env(GITHUB_OAUTH_TOKEN)%', null, 'access_token_header']] 16 | - ['addCache', ['@Symfony\Component\Cache\Adapter\RedisAdapter']] 17 | 18 | Github\HttpClient\Builder: 19 | arguments: 20 | - '@?Psr\Http\Client\ClientInterface' 21 | - '@?Psr\Http\Message\RequestFactoryInterface' 22 | - '@?Psr\Http\Message\StreamFactoryInterface' 23 | 24 | Github\ResultPager: 25 | arguments: 26 | - '@Github\Client' 27 | 28 | Github\ResultPagerInterface: '@Github\ResultPager' 29 | -------------------------------------------------------------------------------- /config/packages/knp_packagist_api.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | Packagist\Api\Client: null 3 | -------------------------------------------------------------------------------- /config/packages/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | channels: ['automerge'] 3 | -------------------------------------------------------------------------------- /config/packages/notifier.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | notifier: 3 | chatter_transports: 4 | slack: '%env(SLACK_DSN)%' 5 | # telegram: '%env(TELEGRAM_DSN)%' 6 | #texter_transports: 7 | # twilio: '%env(TWILIO_DSN)%' 8 | # nexmo: '%env(NEXMO_DSN)%' 9 | channel_policy: 10 | # use chat/slack, chat/telegram, sms/twilio or sms/nexmo 11 | urgent: ['email'] 12 | high: ['email'] 13 | medium: ['email'] 14 | low: ['email'] 15 | admin_recipients: 16 | - {email: admin@example.com} 17 | -------------------------------------------------------------------------------- /config/packages/nyholm_psr7.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | # Register nyholm/psr7 services for autowiring with PSR-17 (HTTP factories) 3 | Psr\Http\Message\RequestFactoryInterface: '@nyholm.psr7.psr17_factory' 4 | Psr\Http\Message\ResponseFactoryInterface: '@nyholm.psr7.psr17_factory' 5 | Psr\Http\Message\ServerRequestFactoryInterface: '@nyholm.psr7.psr17_factory' 6 | Psr\Http\Message\StreamFactoryInterface: '@nyholm.psr7.psr17_factory' 7 | Psr\Http\Message\UploadedFileFactoryInterface: '@nyholm.psr7.psr17_factory' 8 | Psr\Http\Message\UriFactoryInterface: '@nyholm.psr7.psr17_factory' 9 | 10 | # Register nyholm/psr7 services for autowiring with HTTPlug factories 11 | Http\Message\MessageFactory: '@nyholm.psr7.httplug_factory' 12 | Http\Message\RequestFactory: '@nyholm.psr7.httplug_factory' 13 | Http\Message\ResponseFactory: '@nyholm.psr7.httplug_factory' 14 | Http\Message\StreamFactory: '@nyholm.psr7.httplug_factory' 15 | Http\Message\UriFactory: '@nyholm.psr7.httplug_factory' 16 | 17 | nyholm.psr7.psr17_factory: 18 | class: Nyholm\Psr7\Factory\Psr17Factory 19 | 20 | nyholm.psr7.httplug_factory: 21 | class: Nyholm\Psr7\Factory\HttplugFactory 22 | -------------------------------------------------------------------------------- /config/packages/oskarstark_env_var_extension.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | OskarStark\Twig\EnvVarExtension: 3 | tags: ['twig.extension'] 4 | -------------------------------------------------------------------------------- /config/packages/prod/deprecations.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | channels: [deprecation] 3 | handlers: 4 | deprecation: 5 | type: stream 6 | channels: [deprecation] 7 | path: php://stderr 8 | -------------------------------------------------------------------------------- /config/packages/prod/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: fingers_crossed 5 | action_level: error 6 | handler: grouped 7 | excluded_http_codes: [404, 405] 8 | buffer_size: 50 # How many messages should be saved? Prevent memory leaks 9 | grouped: 10 | type: group 11 | members: [nested, slack_errors] 12 | nested: 13 | type: stream 14 | path: php://stderr 15 | level: debug 16 | console: 17 | type: console 18 | process_psr_3_messages: false 19 | channels: ["!event", "!doctrine"] 20 | slack_errors: 21 | type: slack 22 | token: '%env(SLACK_TOKEN)%' 23 | channel: 'dev-kit-logs' 24 | bot_name: '@@dev-kit-logger' 25 | level: debug 26 | include_extra: true 27 | slack: 28 | type: slack 29 | token: '%env(SLACK_TOKEN)%' 30 | channel: 'ci' 31 | bot_name: '@@dev-kit-logger' 32 | level: debug 33 | include_extra: true 34 | use_short_attachment: true 35 | channels: ['automerge'] 36 | -------------------------------------------------------------------------------- /config/packages/prod/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: null 4 | -------------------------------------------------------------------------------- /config/packages/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | utf8: true 4 | 5 | # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. 6 | # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands 7 | #default_uri: http://localhost 8 | -------------------------------------------------------------------------------- /config/packages/test/framework.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | test: true 3 | session: 4 | storage_factory_id: session.storage.factory.mock_file 5 | -------------------------------------------------------------------------------- /config/packages/test/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: fingers_crossed 5 | action_level: error 6 | handler: nested 7 | excluded_http_codes: [404, 405] 8 | channels: ["!event"] 9 | nested: 10 | type: stream 11 | path: "%kernel.logs_dir%/%kernel.environment%.log" 12 | level: debug 13 | -------------------------------------------------------------------------------- /config/packages/test/twig.yaml: -------------------------------------------------------------------------------- 1 | twig: 2 | strict_variables: true 3 | -------------------------------------------------------------------------------- /config/packages/test/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: false 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: 7 | collect: false 8 | -------------------------------------------------------------------------------- /config/packages/twig.yaml: -------------------------------------------------------------------------------- 1 | twig: 2 | default_path: '%kernel.project_dir%/templates' 3 | debug: '%kernel.debug%' 4 | strict_variables: '%kernel.debug%' 5 | exception_controller: null 6 | -------------------------------------------------------------------------------- /config/preload.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) { 15 | require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php'; 16 | } 17 | -------------------------------------------------------------------------------- /config/routes.yaml: -------------------------------------------------------------------------------- 1 | #index: 2 | # path: / 3 | # controller: App\Controller\DefaultController::index 4 | -------------------------------------------------------------------------------- /config/routes/annotations.yaml: -------------------------------------------------------------------------------- 1 | controllers: 2 | resource: ../../src/Controller/ 3 | type: annotation 4 | 5 | kernel: 6 | resource: ../../src/Kernel.php 7 | type: annotation 8 | -------------------------------------------------------------------------------- /config/routes/dev/framework.yaml: -------------------------------------------------------------------------------- 1 | _errors: 2 | resource: '@FrameworkBundle/Resources/config/routing/errors.xml' 3 | prefix: /_error 4 | -------------------------------------------------------------------------------- /config/routes/dev/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler_wdt: 2 | resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml' 3 | prefix: /_wdt 4 | 5 | web_profiler_profiler: 6 | resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml' 7 | prefix: /_profiler 8 | -------------------------------------------------------------------------------- /config/services.yaml: -------------------------------------------------------------------------------- 1 | # This file is the entry point to configure your own services. 2 | # Files in the packages/ subdirectory configure your dependencies. 3 | 4 | # Put parameters here that don't need to change on each machine where the app is deployed 5 | # https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration 6 | parameters: 7 | 8 | services: 9 | # default configuration for services in *this* file 10 | _defaults: 11 | autowire: true # Automatically injects dependencies in your services. 12 | autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. 13 | bind: 14 | string $appDir: '%kernel.project_dir%' 15 | string $devKitToken: '%env(DEV_KIT_TOKEN)%' 16 | string $githubToken: '%env(GITHUB_OAUTH_TOKEN)%' 17 | 18 | # makes classes in src/ available to be used as services 19 | # this creates a service per class whose id is the fully-qualified class name 20 | App\: 21 | resource: '../src/' 22 | exclude: 23 | - '../src/DependencyInjection/' 24 | - '../src/Entity/' 25 | - '../src/Migrations/' 26 | - '../src/Kernel.php' 27 | - '../src/Tests/' 28 | 29 | # controllers are imported separately to make sure services can be injected 30 | # as action arguments even if you don't extend any base controller class 31 | App\Controller\: 32 | resource: '../src/Controller/' 33 | tags: ['controller.service_arguments'] 34 | 35 | # add more service definitions when explicit configuration is needed 36 | # please note that last definitions always *replace* previous ones 37 | App\Command\AutoMergeCommand: 38 | $logger: '@monolog.logger.automerge' 39 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OS=$(uname -s) 4 | 5 | ## 6 | ## Parameters 7 | ## 8 | if [ ! -e ".env" ]; then 9 | cp .env.dist .env 10 | fi 11 | 12 | ## 13 | ## Docker 14 | ## 15 | 16 | # Sed commands were not working on mac osx 17 | # https://stackoverflow.com/questions/34596156/sed-commands-working-on-ubuntu-but-not-on-osx 18 | # Note: -i "" does not work on Ubuntu Linux. 19 | if [ "${OS}" == 'Darwin' ]; then 20 | sed -i "" -e "s/{unix_username}/$(id -un)/" .env 21 | sed -i "" -e "s/{unix_group}/$(id -gn)/" .env 22 | sed -i "" -e "s/{unix_uid}/$(id -u)/" .env 23 | sed -i "" -e "s/{unix_gid}/$(id -g)/" .env 24 | else 25 | sed -i .env -e "s/{unix_username}/$(id -un)/" 26 | sed -i .env -e "s/{unix_group}/$(id -gn)/" 27 | sed -i .env -e "s/{unix_uid}/$(id -u)/" 28 | sed -i .env -e "s/{unix_gid}/$(id -g)/" 29 | fi 30 | -------------------------------------------------------------------------------- /docs/.doctor-rst.yaml: -------------------------------------------------------------------------------- 1 | # DO NOT EDIT THIS FILE! 2 | # 3 | # It's auto-generated by sonata-project/dev-kit package. 4 | 5 | rules: 6 | american_english: ~ 7 | blank_line_after_anchor: ~ 8 | blank_line_after_directive: ~ 9 | blank_line_after_filepath_in_code_block: ~ 10 | composer_dev_option_at_the_end: ~ 11 | ensure_exactly_one_space_between_link_definition_and_link: ~ 12 | ensure_link_definition_contains_valid_url: ~ 13 | ensure_order_of_code_blocks_in_configuration_block: ~ 14 | extend_abstract_admin: ~ 15 | final_admin_class: ~ 16 | final_admin_extension_classes: ~ 17 | kernel_instead_of_app_kernel: ~ 18 | no_admin_yaml: ~ 19 | no_app_bundle: ~ 20 | no_app_console: ~ 21 | no_brackets_in_method_directive: ~ 22 | no_bash_prompt: ~ 23 | no_composer_phar: ~ 24 | no_composer_req: ~ 25 | no_config_yaml: ~ 26 | no_directive_after_shorthand: ~ 27 | no_explicit_use_of_code_block_php: ~ 28 | no_inheritdoc: ~ 29 | no_php_open_tag_in_code_block_php_directive: ~ 30 | no_php_prefix_before_bin_console: ~ 31 | no_space_before_self_xml_closing_tag: ~ 32 | max_colons: ~ 33 | only_backslashes_in_namespace_in_php_code_block: ~ 34 | only_backslashes_in_use_statements_in_php_code_block: ~ 35 | replacement: ~ 36 | short_array_syntax: ~ 37 | typo: ~ 38 | use_deprecated_directive_instead_of_versionadded: ~ 39 | valid_use_statements: ~ 40 | versionadded_directive_should_have_version: ~ 41 | yaml_instead_of_yml_suffix: ~ 42 | yarn_dev_option_at_the_end: ~ 43 | -------------------------------------------------------------------------------- /docs/_static/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonata-project/dev-kit/d66237b0e2fcb1ea6bee8067eaf0ead87e6f3772/docs/_static/.gitignore -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # DO NOT EDIT THIS FILE! 4 | # 5 | # It's auto-generated by sonata-project/dev-kit package. 6 | 7 | from sphinx.highlighting import lexers 8 | from pygments.lexers.web import PhpLexer 9 | 10 | extensions = ['sphinx_rtd_theme'] 11 | templates_path = ['_templates'] 12 | source_suffix = '.rst' 13 | master_doc = 'index' 14 | project = u'Sonata Project' 15 | copyright = u'2010, Thomas Rabaix' 16 | exclude_patterns = ['_build'] 17 | highlight_language = 'php' 18 | 19 | lexers['php'] = PhpLexer(startinline=True) 20 | lexers['php-annotations'] = PhpLexer(startinline=True) 21 | lexers['php-attributes'] = PhpLexer(startinline=True) 22 | lexers['php-standalone'] = PhpLexer(startinline=True) 23 | lexers['php-symfony'] = PhpLexer(startinline=True) 24 | 25 | html_theme = 'sphinx_rtd_theme' 26 | html_static_path = ['_static'] 27 | htmlhelp_basename = 'doc' 28 | -------------------------------------------------------------------------------- /docs/pages/contribute.rst: -------------------------------------------------------------------------------- 1 | Contribute to Sonata Project 2 | ============================ 3 | 4 | Sonata is an open source project with a very diverse community, where people from all over the world 5 | contribute their work, with everyone benefitting from friendly help and advice, and kindly 6 | helping others in return. 7 | 8 | We have two main channels opened: 9 | 10 | * `Symfony Slack `_ on the `#sonata `_ channel 11 | * Github issues (only for bugs and feature request) 12 | 13 | Sonata respects the Symfony's conventions about contributing to the code. 14 | So before going further please review our `Contribution Guide `_. 15 | 16 | How do I help? 17 | -------------- 18 | Are you using Sonata? Do you love Open Source? You can help us in many ways and most of them 19 | don't even require coding skills. 20 | 21 | Bug Report 22 | ~~~~~~~~~~ 23 | If you find a bug we kindly request you to report it. However, before submitting it please 24 | check the project documentation available online. 25 | 26 | Then, if it appears that it's a real bug, you may report it using GitHub. 27 | 28 | We are using templates on Github to report different things, it will ask you for some information 29 | like composer versions installed. Please fill with all the indications provided by the template. 30 | 31 | Documentation 32 | ~~~~~~~~~~~~~ 33 | All the documentation available is generated on each Sonata repository, it requires a lot of 34 | work to keep it up to date. All bundles have its own documentation stored in the same Git repository. 35 | 36 | If you find some mistake or some parts are incomplete, you can open a Pull Request updating the documentation 37 | so everyone can enjoy better docs. 38 | 39 | The documentation is in RST format using `Sphinx `_ and hosted on `readthedocs.org `_. 40 | 41 | Translations 42 | ~~~~~~~~~~~~ 43 | All our bundles have translations in a lot of languages. Sonata translations are kept on each 44 | bundle. 45 | 46 | If you find something that is not translated or the translation can be improved you 47 | can go to the bundle where that translation is located an open a Pull Request. 48 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | ../templates/project/docs/requirements.txt -------------------------------------------------------------------------------- /php.ini: -------------------------------------------------------------------------------- 1 | [Date] 2 | date.timezone = Europe/Paris 3 | -------------------------------------------------------------------------------- /phpstan-baseline.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | ignoreErrors: 3 | - # https://github.com/phpstan/phpstan-strict-rules/issues/130 4 | message: '#^Call to static method PHPUnit\\Framework\\Assert::.* will always evaluate to true\.$#' 5 | path: tests/ 6 | -------------------------------------------------------------------------------- /phpstan-console-application.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | use App\Kernel; 15 | use Symfony\Bundle\FrameworkBundle\Console\Application; 16 | use Symfony\Component\Dotenv\Dotenv; 17 | 18 | require __DIR__.'/vendor/autoload.php'; 19 | 20 | (new Dotenv())->bootEnv(__DIR__.'/.env', 'test'); 21 | 22 | $kernel = new Kernel('test', true); 23 | 24 | return new Application($kernel); 25 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | includes: 2 | - phpstan-baseline.neon 3 | 4 | parameters: 5 | level: 9 6 | symfony: 7 | containerXmlPath: var/cache/dev/App_KernelDevDebugContainer.xml 8 | consoleApplicationLoader: ./phpstan-console-application.php 9 | paths: 10 | - src 11 | - tests 12 | treatPhpDocTypesAsCertain: false 13 | checkInternalClassCaseSensitivity: true 14 | checkMissingVarTagTypehint: true 15 | checkMissingTypehints: true 16 | checkUninitializedProperties: true 17 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | ./tests/ 18 | 19 | 20 | 21 | 22 | 23 | ./src/ 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonata-project/dev-kit/d66237b0e2fcb1ea6bee8067eaf0ead87e6f3772/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonata-project/dev-kit/d66237b0e2fcb1ea6bee8067eaf0ead87e6f3772/public/favicon.png -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | use App\Kernel; 15 | use Symfony\Component\Dotenv\Dotenv; 16 | use Symfony\Component\ErrorHandler\Debug; 17 | use Symfony\Component\HttpFoundation\Request; 18 | 19 | require dirname(__DIR__).'/vendor/autoload.php'; 20 | 21 | (new Dotenv())->bootEnv(dirname(__DIR__).'/.env'); 22 | 23 | if ($_SERVER['APP_DEBUG']) { 24 | umask(0000); 25 | 26 | Debug::enable(); 27 | } 28 | 29 | $kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); 30 | $request = Request::createFromGlobals(); 31 | $response = $kernel->handle($request); 32 | $response->send(); 33 | $kernel->terminate($request, $response); 34 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | use Rector\Config\RectorConfig; 15 | use Rector\Php70\Rector\FunctionLike\ExceptionHandlerTypehintRector; 16 | use Rector\PHPUnit\CodeQuality\Rector\Class_\PreferPHPUnitThisCallRector; 17 | use Rector\PHPUnit\Set\PHPUnitSetList; 18 | use Rector\Set\ValueObject\LevelSetList; 19 | 20 | return static function (RectorConfig $rectorConfig): void { 21 | $rectorConfig->paths([ 22 | __DIR__.'/src', 23 | __DIR__.'/tests', 24 | ]); 25 | 26 | $rectorConfig->sets([ 27 | LevelSetList::UP_TO_PHP_74, 28 | PHPUnitSetList::PHPUNIT_90, 29 | PHPUnitSetList::PHPUNIT_CODE_QUALITY, 30 | ]); 31 | 32 | $rectorConfig->importNames(); 33 | $rectorConfig->importShortClasses(false); 34 | $rectorConfig->skip([ 35 | ExceptionHandlerTypehintRector::class, 36 | PreferPHPUnitThisCallRector::class, 37 | ]); 38 | }; 39 | -------------------------------------------------------------------------------- /src/Action/DetermineNextReleaseVersion.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Action; 15 | 16 | use App\Domain\Value\Stability; 17 | use App\Github\Domain\Value\PullRequest; 18 | use App\Github\Domain\Value\Release\Tag; 19 | 20 | final class DetermineNextReleaseVersion 21 | { 22 | /** 23 | * @param PullRequest[] $pullRequests 24 | */ 25 | public static function forTagAndPullRequests(Tag $current, array $pullRequests): Tag 26 | { 27 | if ([] === $pullRequests) { 28 | return $current; 29 | } 30 | 31 | $stabilities = array_map( 32 | static fn (PullRequest $pr): string => $pr->stability()->toString(), 33 | $pullRequests 34 | ); 35 | 36 | // Add compatibility for non-semantic versioning like `4.0.0-alpha-1` 37 | $currentTag = str_replace('-', '.', $current->toString()); 38 | $parts = explode('.', $currentTag); 39 | 40 | if (isset($parts[3])) { 41 | if (str_contains($parts[3], 'alpha')) { 42 | return Tag::fromString(implode('.', [$parts[0], $parts[1], \sprintf('%s-rc-1', $parts[2])])); 43 | } 44 | 45 | return Tag::fromString(implode('.', [$parts[0], $parts[1], $parts[2]])); 46 | } 47 | 48 | if ('x' === $parts[1]) { 49 | return Tag::fromString(implode('.', [(int) $parts[0] + 1, 0, '0-alpha-1'])); 50 | } 51 | 52 | if (\in_array(Stability::major()->toString(), $stabilities, true)) { 53 | return Tag::fromString(implode('.', [(int) $parts[0] + 1, 0, 0])); 54 | } 55 | 56 | if (\in_array(Stability::minor()->toString(), $stabilities, true)) { 57 | return Tag::fromString(implode('.', [$parts[0], (int) $parts[1] + 1, 0])); 58 | } 59 | 60 | if (\in_array(Stability::patch()->toString(), $stabilities, true)) { 61 | return Tag::fromString(implode('.', [$parts[0], $parts[1], (int) $parts[2] + 1])); 62 | } 63 | 64 | return $current; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Action/Exception/NoPullRequestsMergedSinceLastRelease.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Action\Exception; 15 | 16 | use App\Domain\Value\Branch; 17 | use App\Domain\Value\Project; 18 | 19 | /** 20 | * @author Oskar Stark 21 | */ 22 | final class NoPullRequestsMergedSinceLastRelease extends \RuntimeException 23 | { 24 | public static function forBranch(Project $project, Branch $branch, ?\DateTimeImmutable $lastRelease, ?\Throwable $previous = null): self 25 | { 26 | $since = null !== $lastRelease ? $lastRelease->format('Y-m-d H:i:s') : 'never'; 27 | 28 | return new self( 29 | \sprintf( 30 | 'No pull requests merged since last release "%s" for branch "%s" of project "%s".', 31 | $since, 32 | $branch->name(), 33 | $project->name() 34 | ), 35 | 0, 36 | $previous 37 | ); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Command/AbstractCommand.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Command; 15 | 16 | use Symfony\Component\Console\Command\Command; 17 | use Symfony\Component\Console\Input\InputInterface; 18 | use Symfony\Component\Console\Output\OutputInterface; 19 | use Symfony\Component\Console\Style\SymfonyStyle; 20 | 21 | /** 22 | * @author Sullivan Senechal 23 | */ 24 | abstract class AbstractCommand extends Command 25 | { 26 | public const GITHUB_USER = 'SonataCI'; 27 | public const GITHUB_EMAIL = 'thomas+ci@sonata-project.org'; 28 | public const DEPENDABOT_BOT = 'dependabot[bot]'; 29 | 30 | protected const LABEL_NOTHING_CHANGED = 'Nothing to be changed.'; 31 | 32 | protected SymfonyStyle $io; // @phpstan-ignore-line 33 | 34 | protected function initialize(InputInterface $input, OutputInterface $output): void 35 | { 36 | $this->io = new SymfonyStyle($input, $output); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Command/AbstractNeedApplyCommand.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Command; 15 | 16 | use Symfony\Component\Console\Input\InputInterface; 17 | use Symfony\Component\Console\Input\InputOption; 18 | use Symfony\Component\Console\Output\OutputInterface; 19 | 20 | /** 21 | * @author Sullivan Senechal 22 | */ 23 | abstract class AbstractNeedApplyCommand extends AbstractCommand 24 | { 25 | protected bool $apply = false; 26 | 27 | protected function configure(): void 28 | { 29 | parent::configure(); 30 | 31 | $this->addOption('apply', null, InputOption::VALUE_NONE, 'Applies wanted requests'); 32 | } 33 | 34 | protected function initialize(InputInterface $input, OutputInterface $output): void 35 | { 36 | parent::initialize($input, $output); 37 | 38 | $this->apply = $input->getOption('apply'); 39 | if (!$this->apply) { 40 | $this->io->warning('This is a dry run execution. No change will be applied here.'); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Config/ConfiguredLabels.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Config; 15 | 16 | use App\Github\Domain\Value\Label; 17 | use App\Github\Domain\Value\Label\Color; 18 | use Symfony\Component\Config\Definition\Processor; 19 | use Symfony\Component\Yaml\Yaml; 20 | use Webmozart\Assert\Assert; 21 | 22 | /** 23 | * @author Oskar Stark 24 | */ 25 | final class ConfiguredLabels 26 | { 27 | /** 28 | * @var array 29 | */ 30 | private array $labels = []; 31 | 32 | public function __construct() 33 | { 34 | $processor = new Processor(); 35 | $baseConfig = $processor->processConfiguration(new LabelsConfiguration(), [ 36 | 'sonata' => Yaml::parseFile(__DIR__.'/../../config/labels.yaml'), 37 | ]); 38 | 39 | foreach ($baseConfig['labels'] as $name => $config) { 40 | Assert::string($name); 41 | 42 | $this->labels[$name] = Label::fromValues( 43 | $name, 44 | Color::fromString($config['color']) 45 | ); 46 | } 47 | } 48 | 49 | /** 50 | * @return array 51 | */ 52 | public function all(): array 53 | { 54 | return $this->labels; 55 | } 56 | 57 | public function byName(string $name): Label 58 | { 59 | Assert::stringNotEmpty($name); 60 | Assert::keyExists( 61 | $this->labels, 62 | $name, 63 | \sprintf( 64 | 'Unknown label: %s', 65 | $name 66 | ) 67 | ); 68 | 69 | return $this->labels[$name]; 70 | } 71 | 72 | public function shouldExist(Label $label): bool 73 | { 74 | return \array_key_exists($label->name(), $this->labels); 75 | } 76 | 77 | public function needsUpdate(Label $label): bool 78 | { 79 | if (!$this->shouldExist($label)) { 80 | return false; 81 | } 82 | 83 | $configured = $this->byName($label->name()); 84 | 85 | return !$configured->equals($label); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Config/Exception/UnknownBranch.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Config\Exception; 15 | 16 | use App\Domain\Value\Project; 17 | 18 | /** 19 | * @author Oskar Stark 20 | */ 21 | final class UnknownBranch extends \InvalidArgumentException 22 | { 23 | public static function forName(Project $project, string $name): self 24 | { 25 | return new self(\sprintf( 26 | 'Could not find branch with name "%s" for project "%s".', 27 | $name, 28 | $project->name() 29 | )); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Config/Exception/UnknownProject.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Config\Exception; 15 | 16 | /** 17 | * @author Oskar Stark 18 | */ 19 | final class UnknownProject extends \InvalidArgumentException 20 | { 21 | public static function forName(string $name): self 22 | { 23 | return new self(\sprintf( 24 | 'Could not find project with name "%s".', 25 | $name 26 | )); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Config/LabelsConfiguration.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Config; 15 | 16 | use Symfony\Component\Config\Definition\Builder\TreeBuilder; 17 | use Symfony\Component\Config\Definition\ConfigurationInterface; 18 | 19 | /** 20 | * @author Sullivan Senechal 21 | */ 22 | class LabelsConfiguration implements ConfigurationInterface 23 | { 24 | /** 25 | * @psalm-suppress PossiblyNullReference, UndefinedInterfaceMethod 26 | * 27 | * @see https://github.com/psalm/psalm-plugin-symfony/issues/174 28 | */ 29 | public function getConfigTreeBuilder(): TreeBuilder 30 | { 31 | $treeBuilder = new TreeBuilder('sonata'); 32 | $rootNode = $treeBuilder->getRootNode(); 33 | 34 | $rootNode 35 | ->children() 36 | ->arrayNode('labels') 37 | ->isRequired() 38 | ->requiresAtLeastOneElement() 39 | ->useAttributeAsKey('name') 40 | ->prototype('array') 41 | ->children() 42 | ->scalarNode('color')->isRequired()->cannotBeEmpty()->end() 43 | ->end() 44 | ->end() 45 | ->end() 46 | ->end(); 47 | 48 | return $treeBuilder; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Controller/DefaultController.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Controller; 15 | 16 | use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; 17 | use Symfony\Component\HttpFoundation\Response; 18 | use Symfony\Component\Routing\Annotation\Route; 19 | 20 | final class DefaultController extends AbstractController 21 | { 22 | /** 23 | * @Route("/", name="default") 24 | */ 25 | public function index(): Response 26 | { 27 | return $this->render('default/index.html.twig'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Controller/NextReleaseForProjectController.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Controller; 15 | 16 | use App\Action\DetermineNextRelease; 17 | use App\Action\Exception\NoPullRequestsMergedSinceLastRelease; 18 | use App\Config\Exception\UnknownProject; 19 | use App\Config\Projects; 20 | use Symfony\Component\HttpFoundation\Response; 21 | use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; 22 | use Symfony\Component\Routing\Annotation\Route; 23 | use Twig\Environment; 24 | 25 | final class NextReleaseForProjectController 26 | { 27 | private Projects $projects; 28 | private DetermineNextRelease $determineNextRelease; 29 | private Environment $twig; 30 | 31 | public function __construct(Projects $projects, DetermineNextRelease $determineNextRelease, Environment $twig) 32 | { 33 | $this->projects = $projects; 34 | $this->determineNextRelease = $determineNextRelease; 35 | $this->twig = $twig; 36 | } 37 | 38 | /** 39 | * @Route("/next-release/{projectName}/{branchName}", name="next_release_project") 40 | */ 41 | public function __invoke(string $projectName, string $branchName): Response 42 | { 43 | try { 44 | $project = $this->projects->byName($projectName); 45 | $branch = $project->branch($branchName); 46 | } catch (UnknownProject $e) { 47 | throw new NotFoundHttpException($e->getMessage()); 48 | } 49 | 50 | try { 51 | $release = $this->determineNextRelease->__invoke($project, $branch); 52 | } catch (NoPullRequestsMergedSinceLastRelease $e) { 53 | throw new NotFoundHttpException($e->getMessage()); 54 | } 55 | 56 | $content = $this->twig->render( 57 | 'releases/project.html.twig', 58 | [ 59 | 'release' => $release, 60 | ] 61 | ); 62 | 63 | $response = new Response(); 64 | $response->setContent($content); 65 | 66 | return $response; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Domain/Exception/CannotGenerateChangelog.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Domain\Exception; 15 | 16 | use App\Domain\Value\NextRelease; 17 | 18 | /** 19 | * @author Oskar Stark 20 | */ 21 | final class CannotGenerateChangelog extends \LogicException 22 | { 23 | public static function forRelease(NextRelease $nextRelease, ?\Throwable $previous = null): self 24 | { 25 | return new self( 26 | \sprintf( 27 | 'Release "%s" cannot be released yet. Please check labels and changelogs of the pull requests.', 28 | $nextRelease->nextTag()->toString() 29 | ), 30 | 0, 31 | $previous 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Domain/Exception/NoBranchesAvailable.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Domain\Exception; 15 | 16 | use App\Domain\Value\Project; 17 | 18 | /** 19 | * @author Oskar Stark 20 | */ 21 | final class NoBranchesAvailable extends \InvalidArgumentException 22 | { 23 | public static function forProject(Project $project, ?\Throwable $previous = null): self 24 | { 25 | return new self( 26 | \sprintf( 27 | 'Project "%s" has no branches configured.', 28 | $project->name() 29 | ), 30 | 0, 31 | $previous 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Domain/Value/Changelog/Section.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Domain\Value\Changelog; 15 | 16 | use App\Domain\Value\TrimmedNonEmptyString; 17 | use Webmozart\Assert\Assert; 18 | 19 | /** 20 | * @author Oskar Stark 21 | */ 22 | final class Section 23 | { 24 | private string $headline; 25 | 26 | /** 27 | * @var string[] 28 | */ 29 | private array $lines; 30 | 31 | /** 32 | * @param string[] $lines 33 | */ 34 | private function __construct(string $headline, array $lines) 35 | { 36 | $headline = TrimmedNonEmptyString::fromString($headline)->toString(); 37 | 38 | Assert::oneOf( 39 | $headline, 40 | [ 41 | 'Added', 42 | 'Changed', 43 | 'Deprecated', 44 | 'Fixed', 45 | 'Removed', 46 | 'Security', 47 | ] 48 | ); 49 | 50 | $this->headline = $headline; 51 | 52 | Assert::notEmpty($lines); 53 | $this->lines = $lines; 54 | } 55 | 56 | /** 57 | * @param string[] $lines 58 | */ 59 | public static function fromValues(string $headline, array $lines): self 60 | { 61 | return new self($headline, $lines); 62 | } 63 | 64 | public function headline(): string 65 | { 66 | return $this->headline; 67 | } 68 | 69 | /** 70 | * @return string[] 71 | */ 72 | public function lines(): array 73 | { 74 | return $this->lines; 75 | } 76 | 77 | public function asMarkdown(): string 78 | { 79 | return implode(\PHP_EOL, array_merge( 80 | [ 81 | \sprintf( 82 | '### %s', 83 | $this->headline 84 | ), 85 | ], 86 | $this->lines 87 | )); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Domain/Value/ExcludedFile.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Domain\Value; 15 | 16 | /** 17 | * @author Oskar Stark 18 | */ 19 | final class ExcludedFile 20 | { 21 | private string $filename; 22 | 23 | private function __construct(string $filename) 24 | { 25 | $this->filename = TrimmedNonEmptyString::fromString($filename)->toString(); 26 | } 27 | 28 | public static function fromString(string $filename): self 29 | { 30 | return new self($filename); 31 | } 32 | 33 | public function filename(): string 34 | { 35 | return $this->filename; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Domain/Value/Path.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Domain\Value; 15 | 16 | use Webmozart\Assert\Assert; 17 | 18 | /** 19 | * @author Oskar Stark 20 | */ 21 | final class Path 22 | { 23 | private string $path; 24 | 25 | private function __construct(string $path) 26 | { 27 | $path = TrimmedNonEmptyString::fromString($path)->toString(); 28 | 29 | Assert::notStartsWith($path, '/'); 30 | Assert::notEndsWith($path, '/'); 31 | 32 | $this->path = $path; 33 | } 34 | 35 | public static function fromString(string $path): self 36 | { 37 | return new self($path); 38 | } 39 | 40 | public function toString(): string 41 | { 42 | return $this->path; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Domain/Value/PhpExtension.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Domain\Value; 15 | 16 | final class PhpExtension 17 | { 18 | private string $name; 19 | 20 | private function __construct(string $name) 21 | { 22 | $this->name = TrimmedNonEmptyString::fromString($name)->toString(); 23 | } 24 | 25 | public static function fromString(string $name): self 26 | { 27 | return new self($name); 28 | } 29 | 30 | public function toString(): string 31 | { 32 | return $this->name; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Domain/Value/PhpVersion.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Domain\Value; 15 | 16 | /** 17 | * @author Oskar Stark 18 | */ 19 | final class PhpVersion 20 | { 21 | private string $version; 22 | 23 | private function __construct(string $version) 24 | { 25 | $this->version = TrimmedNonEmptyString::fromString($version)->toString(); 26 | } 27 | 28 | public static function fromString(string $version): self 29 | { 30 | return new self($version); 31 | } 32 | 33 | public function rectorFormat(): string 34 | { 35 | return str_replace('.', '', $this->version); 36 | } 37 | 38 | public function toString(): string 39 | { 40 | return $this->version; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Domain/Value/Stability.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Domain\Value; 15 | 16 | use Webmozart\Assert\Assert; 17 | 18 | use function Symfony\Component\String\u; 19 | 20 | /** 21 | * @author Oskar Stark 22 | */ 23 | final class Stability 24 | { 25 | private string $value; 26 | 27 | private function __construct(string $value) 28 | { 29 | $value = TrimmedNonEmptyString::fromString($value)->toString(); 30 | 31 | $value = u($value)->lower()->toString(); 32 | 33 | Assert::oneOf( 34 | $value, 35 | [ 36 | 'major', 37 | 'minor', 38 | 'patch', 39 | 'pedantic', 40 | 'unknown', 41 | ] 42 | ); 43 | 44 | $this->value = $value; 45 | } 46 | 47 | public static function fromString(string $value): self 48 | { 49 | return new self($value); 50 | } 51 | 52 | public static function major(): self 53 | { 54 | return new self('major'); 55 | } 56 | 57 | public static function minor(): self 58 | { 59 | return new self('minor'); 60 | } 61 | 62 | public static function patch(): self 63 | { 64 | return new self('patch'); 65 | } 66 | 67 | public static function pedantic(): self 68 | { 69 | return new self('pedantic'); 70 | } 71 | 72 | public static function unknown(): self 73 | { 74 | return new self('unknown'); 75 | } 76 | 77 | public function toString(): string 78 | { 79 | return $this->value; 80 | } 81 | 82 | public function toUppercaseString(): string 83 | { 84 | return u($this->value)->upper()->toString(); 85 | } 86 | 87 | public function equals(self $other): bool 88 | { 89 | return $this->value === $other->toString(); 90 | } 91 | 92 | public function notEquals(self $other): bool 93 | { 94 | return !$this->equals($other); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Domain/Value/TrimmedNonEmptyString.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Domain\Value; 15 | 16 | use Webmozart\Assert\Assert; 17 | 18 | use function Symfony\Component\String\u; 19 | 20 | final class TrimmedNonEmptyString 21 | { 22 | private string $value; 23 | 24 | private function __construct(string $value, string $message) 25 | { 26 | $value = u($value)->trim()->toString(); 27 | 28 | Assert::stringNotEmpty($value, $message); 29 | 30 | $this->value = $value; 31 | } 32 | 33 | public static function fromString(string $value, string $message = ''): self 34 | { 35 | return new self($value, $message); 36 | } 37 | 38 | public function toString(): string 39 | { 40 | return $this->value; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Domain/Value/Variant.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Domain\Value; 15 | 16 | use Webmozart\Assert\Assert; 17 | 18 | /** 19 | * @author Oskar Stark 20 | */ 21 | final class Variant 22 | { 23 | private string $package; 24 | private string $version; 25 | 26 | private function __construct(string $package, string $version) 27 | { 28 | Assert::stringNotEmpty($package, 'Package must not be empty!'); 29 | Assert::contains($package, '/', 'Package must contain a "/"!'); 30 | Assert::notStartsWith($package, '/', 'Package must not start with a "/"!'); 31 | Assert::notEndsWith($package, '/', 'Package must not end with a "/"!'); 32 | $this->package = $package; 33 | 34 | $this->version = TrimmedNonEmptyString::fromString($version, 'Version must not be empty!')->toString(); 35 | } 36 | 37 | public static function fromValues(string $package, string $version): self 38 | { 39 | return new self($package, $version); 40 | } 41 | 42 | public function package(): string 43 | { 44 | return $this->package; 45 | } 46 | 47 | public function version(): string 48 | { 49 | return $this->version; 50 | } 51 | 52 | public function toString(): string 53 | { 54 | return \sprintf( 55 | '%s:"%s"', 56 | $this->package, 57 | $this->version 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Github/Api/BranchProtections.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Github\Api; 15 | 16 | use App\Domain\Value\Branch; 17 | use App\Domain\Value\Repository; 18 | use Github\Client as GithubClient; 19 | 20 | /** 21 | * @author Oskar Stark 22 | */ 23 | final class BranchProtections 24 | { 25 | private GithubClient $github; 26 | 27 | public function __construct(GithubClient $github) 28 | { 29 | $this->github = $github; 30 | } 31 | 32 | /** 33 | * @param mixed[] $params 34 | */ 35 | public function update(Repository $repository, Branch $branch, array $params): void 36 | { 37 | $this->github->repo()->protection()->update( 38 | $repository->username(), 39 | $repository->name(), 40 | $branch->name(), 41 | $params 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Github/Api/Branches.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Github\Api; 15 | 16 | use App\Domain\Value\Repository; 17 | use App\Github\Domain\Value\Branch; 18 | use Github\Client as GithubClient; 19 | 20 | /** 21 | * @author Oskar Stark 22 | */ 23 | final class Branches 24 | { 25 | private GithubClient $github; 26 | 27 | public function __construct(GithubClient $github) 28 | { 29 | $this->github = $github; 30 | } 31 | 32 | /** 33 | * @return Branch[] 34 | */ 35 | public function all(Repository $repository): array 36 | { 37 | // Fetch the more detailed information 38 | return array_map( 39 | fn (array $listResponse): Branch => $this->get($repository, $listResponse['name']), 40 | $this->github->repos()->branches($repository->username(), $repository->name()) 41 | ); 42 | } 43 | 44 | public function get(Repository $repository, string $name): Branch 45 | { 46 | $response = $this->github->repo()->branches( 47 | $repository->username(), 48 | $repository->name(), 49 | $name 50 | ); 51 | 52 | return Branch::fromResponse($response); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Github/Api/Checks.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Github\Api; 15 | 16 | use App\Domain\Value\Repository; 17 | use App\Github\Domain\Value\CheckRuns; 18 | use App\Github\Domain\Value\Sha; 19 | use Github\Client as GithubClient; 20 | 21 | /** 22 | * @author Oskar Stark 23 | */ 24 | final class Checks 25 | { 26 | private GithubClient $github; 27 | 28 | public function __construct(GithubClient $github) 29 | { 30 | $this->github = $github; 31 | } 32 | 33 | public function all(Repository $repository, Sha $sha): CheckRuns 34 | { 35 | $response = $this->github->repo()->checkRuns()->allForReference( 36 | $repository->username(), 37 | $repository->name(), 38 | $sha->toString() 39 | ); 40 | 41 | return CheckRuns::fromResponse($response); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Github/Api/Commits.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Github\Api; 15 | 16 | use App\Domain\Value\Branch; 17 | use App\Domain\Value\Repository; 18 | use App\Github\Domain\Value\Commit; 19 | use App\Github\Domain\Value\PullRequest; 20 | use Github\Client as GithubClient; 21 | use Github\ResultPagerInterface; 22 | use Webmozart\Assert\Assert; 23 | 24 | /** 25 | * @author Oskar Stark 26 | */ 27 | final class Commits 28 | { 29 | private GithubClient $github; 30 | private ResultPagerInterface $githubPager; 31 | 32 | public function __construct(GithubClient $github, ResultPagerInterface $githubPager) 33 | { 34 | $this->github = $github; 35 | $this->githubPager = $githubPager; 36 | } 37 | 38 | /** 39 | * @return Commit[] 40 | */ 41 | public function all(Repository $repository, PullRequest $pullRequest): array 42 | { 43 | return array_map( 44 | static fn (array $response): Commit => Commit::fromResponse($response), 45 | $this->githubPager->fetchAll($this->github->pullRequest(), 'commits', [ 46 | $repository->username(), 47 | $repository->name(), 48 | $pullRequest->issue()->toInt(), 49 | ]) 50 | ); 51 | } 52 | 53 | public function lastCommit(Repository $repository, PullRequest $pullRequest): Commit 54 | { 55 | $allCommits = $this->all($repository, $pullRequest); 56 | $lastCommit = end($allCommits); 57 | Assert::notFalse($lastCommit); 58 | 59 | return $lastCommit; 60 | } 61 | 62 | /** 63 | * @return array 64 | */ 65 | public function compare(Repository $repository, Branch $branch, Branch $branchToCompare): array 66 | { 67 | return $this->github->repos()->commits()->compare( 68 | $repository->username(), 69 | $repository->name(), 70 | $branch->name(), 71 | $branchToCompare->name() 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Github/Api/Hooks.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Github\Api; 15 | 16 | use App\Domain\Value\Repository; 17 | use App\Github\Domain\Value\Hook; 18 | use Github\Client as GithubClient; 19 | 20 | /** 21 | * @author Oskar Stark 22 | */ 23 | final class Hooks 24 | { 25 | private GithubClient $github; 26 | 27 | public function __construct(GithubClient $github) 28 | { 29 | $this->github = $github; 30 | } 31 | 32 | /** 33 | * @return Hook[] 34 | */ 35 | public function all(Repository $repository): array 36 | { 37 | return array_map( 38 | static fn (array $response): Hook => Hook::fromResponse($response), 39 | $this->github->repo()->hooks()->all($repository->username(), $repository->name()) 40 | ); 41 | } 42 | 43 | /** 44 | * @param mixed[] $params 45 | */ 46 | public function create(Repository $repository, array $params): void 47 | { 48 | $this->github->repo()->hooks()->create( 49 | $repository->username(), 50 | $repository->name(), 51 | $params 52 | ); 53 | } 54 | 55 | /** 56 | * @param mixed[] $params 57 | */ 58 | public function update(Repository $repository, Hook $hook, array $params): void 59 | { 60 | $this->github->repo()->hooks()->update( 61 | $repository->username(), 62 | $repository->name(), 63 | $hook->id(), 64 | $params 65 | ); 66 | } 67 | 68 | public function remove(Repository $repository, Hook $hook): void 69 | { 70 | $this->github->repo()->hooks()->remove( 71 | $repository->username(), 72 | $repository->name(), 73 | $hook->id() 74 | ); 75 | } 76 | 77 | public function ping(Repository $repository, Hook $hook): void 78 | { 79 | $this->github->repo()->hooks()->ping( 80 | $repository->username(), 81 | $repository->name(), 82 | $hook->id() 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Github/Api/Issues.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace App\Github\Api; 15 | 16 | use App\Domain\Value\Repository; 17 | use App\Github\Domain\Value\Issue; 18 | use App\Github\Domain\Value\Label; 19 | use Github\Client as GithubClient; 20 | use Github\Exception\RuntimeException; 21 | 22 | /** 23 | * @author Oskar Stark 24 | */ 25 | final class Issues 26 | { 27 | private GithubClient $github; 28 | 29 | public function __construct(GithubClient $github) 30 | { 31 | $this->github = $github; 32 | } 33 | 34 | /** 35 | * @param array