From 48d013a6d6f65970bb0e37181cf70ed9af2759ed Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Wed, 2 Oct 2024 11:56:38 -0700 Subject: [PATCH] feat: add script to execute cs fixer actions --- .github/workflows/code-standards.yml | 23 +++- .php-cs-fixer.default.php | 14 +- composer.json | 7 +- scripts/php-tools | 32 +++++ src/Utils/Actions/RunCsFixerCommand.php | 171 ++++++++++++++++++++++++ 5 files changed, 236 insertions(+), 11 deletions(-) create mode 100755 scripts/php-tools create mode 100644 src/Utils/Actions/RunCsFixerCommand.php diff --git a/.github/workflows/code-standards.yml b/.github/workflows/code-standards.yml index 6ebe564..dce7ca4 100644 --- a/.github/workflows/code-standards.yml +++ b/.github/workflows/code-standards.yml @@ -54,6 +54,7 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: '8.2' + # install Google Cloud Tools if a config is provided which starts with "GoogleCloudPlatform/php-tools/" - if: ${{ startsWith(inputs.config, 'GoogleCloudPlatform/php-tools/') }} name: Install Google Cloud Tools run: | @@ -62,18 +63,32 @@ jobs: echo "CONFIG=$HOME/.composer/vendor/google/cloud-tools/${BRANCH%@*}" >> $GITHUB_ENV - name: 'Setup jq' uses: dcarbone/install-jq-action@v2 + - name: Install PHP CS Fixer + run: composer global require friendsofphp/php-cs-fixer:${{ inputs.version }} - name: Run PHP CS Fixer run: | - composer global require friendsofphp/php-cs-fixer:${{ inputs.version }} + # set environment variables in script export RULES=$(echo $'${{ inputs.rules }} ${{ inputs.add-rules }}'|tr -d '\n\t\r '|jq -s '.[0] * .[1]' -crM) export EXCLUDE_PATTERNS=$(echo $'${{ inputs.exclude-patterns }}'|tr -d '\n\t\r ') - CMD_PATH=$([ "$EXCLUDE_PATTERNS" == "" ] && echo "$CONFIG_PATH") + + # use config path only if EXCLUDE_PATTERN is empty + CMD_PATH=$([ "$EXCLUDE_PATTERNS" = "" ] && echo "$CONFIG_PATH" || echo "") CONFIG_OR_RULES=$([ ! -z "$CONFIG" ] && echo "--config=$CONFIG" || echo --rules=$RULES) - echo "TEST" - set -x + # do not fail if php-cs-fixer fails (so we can print debugging info) + set +e ~/.composer/vendor/bin/php-cs-fixer fix \ $CMD_PATH \ $CONFIG_OR_RULES \ --dry-run --diff + + if [ "$?" -ne 0 ]; then + echo "Run this script locally by executing the following command" \ + "from the root of your ${{ github.repository }} repo:" + echo "" + echo " composer global require google/cloud-tools" + echo " ~/.composer/vendor/bin/php-tools cs-fixer ${{ github.repository }} --ref ${{ github.ref_name }}" + echo "" + exit 1 + fi diff --git a/.php-cs-fixer.default.php b/.php-cs-fixer.default.php index b8321bf..2f09864 100644 --- a/.php-cs-fixer.default.php +++ b/.php-cs-fixer.default.php @@ -2,18 +2,24 @@ use Symfony\Component\Yaml\Yaml; -if (!$rulesJson = getenv('RULES')) { +$rulesJson = getenv('RULES'); +$configPath = getenv('CONFIG_PATH'); +$excludePatternsJson = getenv('EXCLUDE_PATTERNS'); + +if (!$rulesJson) { + // Use default rules $workflow = Yaml::parse(file_get_contents(__DIR__ . '/.github/workflows/code-standards.yml')); $rulesJson = $workflow['on']['workflow_call']['inputs']['rules']['default']; } -$excludePatterns = json_decode(getenv('EXCLUDE_PATTERNS') ?: '[]', true); +$rules = json_decode($rulesJson, true); +$excludePatterns = json_decode($excludePatternsJson ?: '[]', true); return (new PhpCsFixer\Config()) - ->setRules(json_decode($rulesJson, true)) + ->setRules($rules) ->setFinder( PhpCsFixer\Finder::create() - ->in(getenv('CONFIG_PATH') ?: __DIR__) + ->in($configPath ?: __DIR__) ->notPath($excludePatterns) ) ; diff --git a/composer.json b/composer.json index 08bbc4d..d0bc6aa 100644 --- a/composer.json +++ b/composer.json @@ -19,13 +19,15 @@ "symfony/console": "^5.0", "symfony/filesystem": "^5.0", "symfony/process": "^5.0", + "symfony/yaml": "^5", "twig/twig": "~3.0" }, "bin": [ "src/Utils/Flex/flex_exec", "src/Utils/WordPress/wp-gae", "scripts/dump_credentials.php", - "scripts/install_test_deps.sh" + "scripts/install_test_deps.sh", + "scripts/php-tools" ], "autoload": { "psr-4": { @@ -43,7 +45,6 @@ "friendsofphp/php-cs-fixer": "^3.62", "google/cloud-dlp": "^1.10", "google/cloud-storage": "^1.33", - "google/cloud-secret-manager": "^1.12", - "symfony/yaml": "^5" + "google/cloud-secret-manager": "^1.12" } } diff --git a/scripts/php-tools b/scripts/php-tools new file mode 100755 index 0000000..0271e61 --- /dev/null +++ b/scripts/php-tools @@ -0,0 +1,32 @@ +#!/usr/bin/env php +add(new RunCsFixerCommand()); +$app->run(); diff --git a/src/Utils/Actions/RunCsFixerCommand.php b/src/Utils/Actions/RunCsFixerCommand.php new file mode 100644 index 0000000..9beb62f --- /dev/null +++ b/src/Utils/Actions/RunCsFixerCommand.php @@ -0,0 +1,171 @@ +setName('cs-fixer') + ->setDescription('Execute a command with the deployed image') + ->addArgument( + 'repo', + InputArgument::REQUIRED, + 'The name of the repo to run the CS fixer for' + ) + ->addOption( + 'workflow-file', + '', + InputOption::VALUE_REQUIRED, + 'name of the github workflow file which contains the configuration', + 'lint.yaml' + ) + ->addOption( + 'ref', + '', + InputOption::VALUE_REQUIRED, + 'The branch of the repo run the CS fixer for', + 'main' + ) + ->addOption( + 'flags', + '', + InputOption::VALUE_REQUIRED, + 'The flags to pass down to the CS fixer', + '--dry-run --diff' + ); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $repo = $input->getArgument('repo'); + if (false === strpos($repo, '/')) { + throw new \Exception('Invalid repo name. Use the format: owner/repo'); + } + + $url = sprintf( + 'https://raw.githubusercontent.com/%s/%s/.github/workflows/%s', + $repo, + $input->getOption('ref'), + $input->getOption('workflow-file'), + ); + + $client = new Client(['http_errors' => false]); + $response = $client->request('GET', $url); + if ($response->getStatusCode() === 404) { + throw new \Exception(sprintf( + 'Failed to fetch the workflow file at "%s", maybe it doesn\'t exist? ' + . 'Try supplying the "--workflow-file" option.', + $url + )); + } + $workflow = Yaml::parse($response->getBody()); + $job = null; + + foreach ($workflow['jobs'] as $id => $workflowJob) { + if (str_contains($workflowJob['uses'] ?? '', '.github/workflows/code-standards.yml')) { + $output->writeln(sprintf('Found job "%s"', $workflowJob['name'] ?? $id)); + $job = $workflowJob; + break; + } + } + if (!$job) { + throw new \Exception('No job found for php-tools/code-standards.yaml in the workflow file'); + } + + // get the default config + $defaultWorkflow = Yaml::parse(file_get_contents(__DIR__ . '/../../../.github/workflows/code-standards.yml')); + $defaults = []; + foreach ($defaultWorkflow['on']['workflow_call']['inputs'] as $name => $inputOptions) { + $defaults[$name] = $inputOptions['default'] ?? ''; + } + $options = array_merge($defaults, $job['with']); + + if (str_starts_with($options['config'], 'GoogleCloudPlatform/php-tools/')) { + // use local file + $options['config'] = str_replace( + 'GoogleCloudPlatform/php-tools/', + __DIR__ . '/../../../', + $options['config'] + ); + // strip branch (we'll ignore it in favor of the current branch) + if (false !== $i = strpos($options['config'], '@')) { + $options['config'] = substr($options['config'], 0, $i); + } + if (!file_exists($options['config'])) { + throw new \Exception('config file not found: ' . realpath($options['config'])); + } + } + + // go through config options and set env vars accordingly + $rules = json_encode(array_merge( + json_decode($options['rules'], true), + json_decode($options['add-rules'], true) + )); + + $excludePatterns = str_replace(["\n", ' '], '', $options['exclude-patterns']); + + // use config path only if EXCLUDE_PATTERN is empty + if ($options['config']) { + // set environment variables so they're available in the CONFIG file + $env = sprintf( + 'CONFIG_PATH=%s RULES=$\'%s\' EXCLUDE_PATTERNS=$\'%s\'', + $options['path'], + $rules, + $excludePatterns, + ); + // Run command using the --config flag + $cmd = sprintf( + '%s ~/.composer/vendor/bin/php-cs-fixer fix --config=%s %s', + $env, + $options['config'], + $input->getOption('flags') + ); + } else { + // Run command using the --rules flag + $cmd = sprintf( + '~/.composer/vendor/bin/php-cs-fixer fix %s --rules=$\'%s\' %s', + $options['path'], + $rules, + $input->getOption('flags') + ); + } + + $output->writeln('Executing the following command: '); + $output->writeln(''); + $output->writeln("\t" . $cmd . ''); + $output->writeln(''); + + // @TODO use Symfony process component to run this + passthru($cmd, $resultCode); + + return $resultCode; + } +}