diff --git a/workflows/ci.yml b/workflows/ci.yml new file mode 100644 index 0000000..b81dca3 --- /dev/null +++ b/workflows/ci.yml @@ -0,0 +1,287 @@ +name: ci +on: + workflow_call: + inputs: + run_ecs: + required: false + default: false + type: boolean + run_phpstan: + required: false + default: false + type: boolean + run_tests: + required: false + default: false + type: boolean + php_versions: + required: false + default: '[]' + type: string + secrets: + slack_webhook_url: + required: true + workflow_dispatch: + pull_request: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }}-1 + cancel-in-progress: true + +env: + SLACK_NOTIFICATION_TITLE: '<{run_url}|build> has {status_message}' + SLACK_NOTIFICATION_MESSAGE_FORMAT: '{emoji} <{commit_url}|{commit_sha}> {status_message} in <{repo_url}|{repo}> on {branch}' + SLACK_NOTIFICATION_FOOTER: '<{repo_url}|{repo}>' + SLACK_NOTIFICATION_ERROR_LEVELS: 'failure,warnings' + SLACK_NOTIFICATION_GROUP_ID: 'SGFL9NKNZ' + +jobs: + + # Run ECS checks outside of the matrix + ecs: + runs-on: ubuntu-latest + if: ${{ inputs.run_ecs }} + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Composer dependencies + run: composer install --no-interaction --no-ansi --no-progress + + - name: Run ECS checks + run: vendor/bin/ecs check --ansi + + - name: Cancel current workflow run + if: ${{ failure() }} + uses: actions/github-script@v4 + with: + script: | + github.actions.cancelWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.runId + }) + + - name: Slack notification failure + uses: ravsamhq/notify-slack-action@v1 + # Only send Slack notification on failure if it's on a branch + if: ${{ ! success() && startsWith(github.ref, 'refs/heads/') }}}} + with: + status: ${{ job.status }} + notification_title: ${{ env.SLACK_NOTIFICATION_TITLE }} + message_format: ${{ env.SLACK_NOTIFICATION_MESSAGE_FORMAT }} + footer: ${{ env.SLACK_NOTIFICATION_FOOTER }} + notify_when: ${{ env.SLACK_NOTIFICATION_ERROR_LEVELS }} + mention_groups: ${{ env.SLACK_NOTIFICATION_GROUP_ID }} + mention_groups_when: ${{ env.SLACK_NOTIFICATION_ERROR_LEVELS }} + env: + SLACK_WEBHOOK_URL: ${{ secrets.slack_webhook_url }} + + # Run PHPStan checks outside of the matrix + phpstan: + runs-on: ubuntu-latest + if: ${{ inputs.run_phpstan }} + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Composer dependencies + run: composer install --no-interaction --no-ansi --no-progress + + - name: Copy tests .env + if: ${{ inputs.run_tests }} + run: cp ./tests/.env.example.mysql ./tests/.env + + - name: Create folders + if: ${{ inputs.run_tests }} + run: mkdir tests/_craft/storage/runtime/compiled_classes + + - name: Build Codeception support files + if: ${{ inputs.run_tests }} + run: "vendor/bin/codecept build" + + - name: Run PHPStan checks + run: "vendor/bin/phpstan --memory-limit=2G" + + - name: Cancel current workflow run + if: ${{ failure() }} + uses: actions/github-script@v4 + with: + script: | + github.actions.cancelWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.runId + }) + + - name: Slack notification failure + uses: ravsamhq/notify-slack-action@v1 + # Only send Slack notification on failure if it's on a branch + if: ${{ ! success() && startsWith(github.ref, 'refs/heads/') }}}} + with: + status: ${{ job.status }} + notification_title: ${{ env.SLACK_NOTIFICATION_TITLE }} + message_format: ${{ env.SLACK_NOTIFICATION_MESSAGE_FORMAT }} + footer: ${{ env.SLACK_NOTIFICATION_FOOTER }} + notify_when: ${{ env.SLACK_NOTIFICATION_ERROR_LEVELS }} + mention_groups: ${{ env.SLACK_NOTIFICATION_GROUP_ID }} + mention_groups_when: ${{ env.SLACK_NOTIFICATION_ERROR_LEVELS }} + env: + SLACK_WEBHOOK_URL: ${{ secrets.slack_webhook_url }} + + # Run Craft tests in the matrix + tests: + runs-on: ${{ matrix.operating-system }} + if: ${{ inputs.run_tests }} + + strategy: + matrix: + operating-system: [ 'ubuntu-latest' ] + php-versions: ${{ fromJSON(inputs.php_versions) }} + db: [ 'mysql', 'pgsql' ] + + name: PHP ${{ matrix.php-versions }} on ${{ matrix.db }} + env: + PHP_EXTENSIONS: ctype,curl,dom,iconv,imagick,intl,json,mbstring,openssl,pcre,pdo,reflection,spl,zip + + services: + + # Install Postgres + pgsql: + image: postgres:latest + env: + POSTGRES_USER: root + POSTGRES_PASSWORD: mysecretpassword + POSTGRES_DB: craft_test + ports: + - 5432:5432 + # Set health checks to wait until Postgres has started + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3 + + # Install MySQL + mysql: + image: bitnami/mysql:latest + env: + MYSQL_ROOT_PASSWORD: mysecretpassword + MYSQL_DATABASE: craft_test + MYSQL_AUTHENTICATION_PLUGIN: mysql_native_password + # Set health checks to wait until mysql has started + options: --health-cmd="mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 5 + ports: + - 3306:3306 + + steps: + + - name: Checkout + uses: actions/checkout@v3 + + - name: Set default test command environment variable + run: | + echo "TEST_COMMAND=./vendor/bin/codecept run unit,functional --fail-fast" >> $GITHUB_ENV + + - name: Set test command for the code coverage environment + run: | + echo "TEST_COMMAND=./vendor/bin/codecept run unit,functional --fail-fast --coverage-xml coverage.xml;" >> $GITHUB_ENV + if: ${{ matrix.php-versions == '8.0' && matrix.db == 'mysql' }} + + - name: Setup cache environment + id: extcache + uses: shivammathur/cache-extensions@257e3c93fb0a7d302495902b976b1d0245123b59 + with: + php-version: ${{ matrix.php-versions }} + extensions: ${{ env.PHP_EXTENSIONS }} + key: extension-cache-v4 # change to clear the extension cache. + + - name: Cache extensions + uses: actions/cache@v3 + with: + path: ${{ steps.extcache.outputs.dir }} + key: ${{ steps.extcache.outputs.key }} + restore-keys: ${{ steps.extcache.outputs.key }} + + - name: Setup PHP + uses: shivammathur/setup-php@verbose + with: + php-version: ${{ matrix.php-versions }} + extensions: ${{ env.PHP_EXTENSIONS }} + ini-values: post_max_size=256M, max_execution_time=180, memory_limit=512M + tools: composer:v2 + env: + COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Print PHP version + run: echo ${{ steps.setup-php.outputs.php-version }} + + - name: Setup problem matchers for PHP + run: echo "::add-matcher::${{ runner.tool_cache }}/php.json" + + - name: Copy tests .env + run: cp ./tests/.env.example.${{ matrix.db }} ./tests/.env + + - name: Update .env creds + run: sed -i 's/DB_PASSWORD=/DB_PASSWORD=mysecretpassword/' tests/.env + + - name: Cache Composer dependencies + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + - uses: actions/cache@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Set Node + uses: actions/setup-node@v1 + with: + node-version: 16.x + + - name: Install Composer dependencies + run: composer install --no-interaction --no-ansi --no-progress + + - name: Install NPM dependencies + run: | + npm cache clean -f + npm install -g npm + + - name: Run tests + run: ${{ env.TEST_COMMAND }} + + # Only upload code coverage if PHP 7.4 and MySQL are selected + - name: Upload coverage to Codecov + if: ${{ matrix.php-versions == '7.4' && matrix.db == 'mysql' }} + uses: codecov/codecov-action@v2 + + - name: Slack notification failure + uses: ravsamhq/notify-slack-action@v1 + # Only send Slack notification on failure if it's on a branch + if: ${{ ! success() && startsWith(github.ref, 'refs/heads/') }}}} + with: + status: ${{ job.status }} + notification_title: ${{ env.SLACK_NOTIFICATION_TITLE }} + message_format: ${{ env.SLACK_NOTIFICATION_MESSAGE_FORMAT }} + footer: ${{ env.SLACK_NOTIFICATION_FOOTER }} + notify_when: ${{ env.SLACK_NOTIFICATION_ERROR_LEVELS }} + mention_groups: ${{ env.SLACK_NOTIFICATION_GROUP_ID }} + mention_groups_when: ${{ env.SLACK_NOTIFICATION_ERROR_LEVELS }} + env: + SLACK_WEBHOOK_URL: ${{ secrets.slack_webhook_url }} + + notify_success: + if: success() + runs-on: ubuntu-latest + needs: [ ecs, phpstan, tests] + steps: + - name: Slack notification success + uses: ravsamhq/notify-slack-action@v1 + with: + status: ${{ job.status }} + notification_title: ${{ env.SLACK_NOTIFICATION_TITLE }} + message_format: ${{ env.SLACK_NOTIFICATION_MESSAGE_FORMAT }} + footer: ${{ env.SLACK_NOTIFICATION_FOOTER }} + notify_when: 'success' + env: + SLACK_WEBHOOK_URL: ${{ secrets.slack_webhook_url }}