From 8a0b8381d5435f9152e71e2dc437e604aebef9e2 Mon Sep 17 00:00:00 2001 From: Tomas Votruba Date: Tue, 28 Feb 2023 16:55:40 +0000 Subject: [PATCH] [DX] Add "setup-ci" command to ease integration to CI (#3425) --- .github/workflows/rector.yaml | 26 +------ phpstan.neon | 3 + src/Console/Command/SetupCICommand.php | 95 +++++++++++++++++++++++ templates/rector-github-action-check.yaml | 48 ++++++++++++ 4 files changed, 147 insertions(+), 25 deletions(-) create mode 100644 src/Console/Command/SetupCICommand.php create mode 100644 templates/rector-github-action-check.yaml diff --git a/.github/workflows/rector.yaml b/.github/workflows/rector.yaml index 4c840b22e7c..c76a47efbdc 100644 --- a/.github/workflows/rector.yaml +++ b/.github/workflows/rector.yaml @@ -1,24 +1,8 @@ -#### -# Due to some Github Actions limitations, we are running realtime fixes (commits) only for self-owned-pr -# -# Current limitations: -# - Secrets (ACCESS_TOKEN) are not available in PRs from forks -# - Github Token has Read-only access (can not commit), Personal Access Token must be used instead -# - Github Token does not trigger workflows after push -# -# So we basically have chicken-egg problem here -# -# https://help.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#permissions-for-the-github_token -#### name: Rector on: pull_request: null -env: - # see https://github.com/composer/composer/issues/9368#issuecomment-718112361 - COMPOSER_ROOT_VERSION: "dev-main" - jobs: rector: # Don't run on forks. @@ -33,23 +17,15 @@ jobs: - config utils runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 8 steps: - # workaround for missing secret in fork PRs - see https://github.com/actions/checkout/issues/298 - # see https://github.com/rectorphp/rector/commit/d395e1c28b8e6a56711dcc2e10490a82965850e4 - - if: github.event.pull_request.head.repo.full_name == github.repository uses: actions/checkout@v3 with: # Must be used to trigger workflow after push token: ${{ secrets.ACCESS_TOKEN }} - # in forks, the token is not available - so we cannot us eit - - - if: github.event.pull_request.head.repo.full_name != github.repository - uses: actions/checkout@v3 - - uses: shivammathur/setup-php@v2 with: diff --git a/phpstan.neon b/phpstan.neon index 41bf9cfac7c..8d9ac46fead 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -820,3 +820,6 @@ parameters: - message: '#Anonymous function has an unused use \$container#' path: src/Autoloading/BootstrapFilesIncluder.php + + # false positive on enums + - '#Method Rector\\Core\\Console\\Command\\SetupCICommand\:\:resolveCurrentCI\(\) never returns (.*?) so it can be removed from the return type#' diff --git a/src/Console/Command/SetupCICommand.php b/src/Console/Command/SetupCICommand.php new file mode 100644 index 00000000000..061e09cb1f7 --- /dev/null +++ b/src/Console/Command/SetupCICommand.php @@ -0,0 +1,95 @@ +.*?)\.git#'; + + public function __construct( + private readonly SymfonyStyle $symfonyStyle + ) { + parent::__construct(); + } + + protected function configure(): void + { + $this->setName('setup-ci'); + $this->setDescription('Add CI workflow to let Rector work for you'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + // detect current CI + $ci = $this->resolveCurrentCI(); + if ($ci === null) { + $this->symfonyStyle->error('No CI detected'); + return self::FAILURE; + } + + if ($ci === CiDetector::CI_GITHUB_ACTIONS) { + $rectorWorkflowFilePath = getcwd() . '/.github/workflows/rector.yaml'; + if (file_exists($rectorWorkflowFilePath)) { + $this->symfonyStyle->warning('The "rector.yaml" workflow already exists'); + return self::SUCCESS; + } + + $currentRepository = $this->resolveCurrentRepositoryName(getcwd()); + if ($currentRepository === null) { + $this->symfonyStyle->error('Current repository name could not be resolved'); + + return self::FAILURE; + } + + $workflowTemplate = FileSystem::read(__DIR__ . '/../../../templates/rector-github-action-check.yaml'); + + $workflowContents = strtr($workflowTemplate, [ + '__CURRENT_REPOSITORY__' => $currentRepository, + ]); + + FileSystem::write($rectorWorkflowFilePath, $workflowContents); + $this->symfonyStyle->success('The "rector.yaml" workflow was added'); + } + + return Command::SUCCESS; + } + + /** + * @return CiDetector::CI_*|null + */ + private function resolveCurrentCI(): ?string + { + if (file_exists(getcwd() . '/.github')) { + return CiDetector::CI_GITHUB_ACTIONS; + } + + return null; + } + + private function resolveCurrentRepositoryName(string $currentDirectory): ?string + { + // resolve current repository name + $process = new Process(['git', 'remote', 'get-url', 'origin'], $currentDirectory, null, null, null); + $process->run(); + + $output = $process->getOutput(); + + $match = Strings::match($output, self::GITHUB_REPOSITORY_REGEX); + return $match['repository_name'] ?? null; + } +} diff --git a/templates/rector-github-action-check.yaml b/templates/rector-github-action-check.yaml new file mode 100644 index 00000000000..e1a4db2aea3 --- /dev/null +++ b/templates/rector-github-action-check.yaml @@ -0,0 +1,48 @@ +# github action that checks code with Rector +name: Rector + +on: + pull_request: null + + +jobs: + rector: + # Don't run on forks. + if: github.repository == '__CURRENT_REPOSITORY__' + + runs-on: ubuntu-latest + + steps: + - + uses: actions/checkout@v3 + with: + # needed to trigger workflow after push + token: ${{ secrets.ACCESS_TOKEN }} + + - + uses: shivammathur/setup-php@v2 + with: + php-version: 8.1 + coverage: none + + - uses: "ramsey/composer-install@v2" + + ## First run Rector - here can't be --dry-run !!! it would stop the job with it and not commit anything in the future + - run: vendor/bin/rector --ansi + + - run: vendor/bin/ecs check --fix --ansi + + # see https://github.com/EndBug/add-and-commit + - + # commit only to core contributors who have repository access + if: github.event.pull_request.head.repo.full_name == github.repository + uses: EndBug/add-and-commit@v7.5.0 + with: + # The arguments for the `git add` command (see the paragraph below for more info) + add: . + message: "[rector] Rector fixes" + author_name: "GitHub Action" + author_email: "action@github.com" + env: + # to get push access + GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}