diff --git a/.gitattributes b/.gitattributes index a489af0..0132d8d 100755 --- a/.gitattributes +++ b/.gitattributes @@ -3,12 +3,12 @@ # Ignore all test and documentation with "export-ignore". /.github export-ignore +/bin/coverage-badge export-ignore /docs export-ignore /tests export-ignore /.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore -/.gitlab-ci.yml export-ignore /CODE_OF_CONDUCT.md export-ignore /CONTRIBUTING.md export-ignore /docker-compose.yml export-ignore diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c7fc597 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,112 @@ +name: Build Pipeline + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + composer: + runs-on: ubuntu-latest + container: spaceonfire/nginx-php-fpm:latest-7.2 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Validate composer.json + run: composer validate + + codestyle: + runs-on: ubuntu-latest + container: spaceonfire/nginx-php-fpm:latest-7.2 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v2 + env: + COMPOSER_CACHE_KEY: 'composer-7.2' + with: + path: vendor + key: ${{ env.COMPOSER_CACHE_KEY }} + restore-keys: ${{ env.COMPOSER_CACHE_KEY }} + + - name: Install dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer install --prefer-dist --no-progress --no-suggest + + - name: Check coding standard + run: vendor/bin/ecs check --no-progress-bar --no-interaction + + phpstan: + runs-on: ubuntu-latest + container: spaceonfire/nginx-php-fpm:latest-7.2 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v2 + env: + COMPOSER_CACHE_KEY: 'composer-7.2' + with: + path: vendor + key: ${{ env.COMPOSER_CACHE_KEY }} + restore-keys: ${{ env.COMPOSER_CACHE_KEY }} + + - name: Install dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer install --prefer-dist --no-progress --no-suggest + + - name: PHPStan + run: vendor/bin/phpstan analyse --no-progress --no-interaction + + phpunit: + runs-on: ubuntu-latest + strategy: + matrix: + php-version: + - '7.2' + - '7.3' + - '7.4' + container: spaceonfire/nginx-php-fpm:latest-${{ matrix.php-version }} + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v2 + env: + COMPOSER_CACHE_KEY: 'composer-${{ matrix.php-version }}' + with: + path: vendor + key: ${{ env.COMPOSER_CACHE_KEY }} + restore-keys: ${{ env.COMPOSER_CACHE_KEY }} + + - name: Install dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer install --prefer-dist --no-progress --no-suggest + + - name: PHPUnit + run: | + apk update + docker-php-ext-enable xdebug + vendor/bin/phpunit --no-interaction + cat build/coverage.txt + + - name: PHPUnit Artifacts + uses: actions/upload-artifact@v2 + with: + name: phpunit-${{ matrix.php-version }} + path: build/ + + - name: Generate coverage badge + if: matrix.php-version == '7.2' && github.event_name == 'push' && github.ref == 'refs/heads/master' + run: php bin/coverage-badge ${{ github.repository }}.json ${{ secrets.COVERAGE_GIST_ID }} ${{ secrets.COVERAGE_GIST_TOKEN }} diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100755 index 70c9736..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,53 +0,0 @@ -stages: - - test - -.in-docker-job: - tags: - - docker - image: alpine - -.php-job: - extends: .in-docker-job - image: spaceonfire/nginx-php-fpm:latest-7.2 - -.composer-job: - extends: .php-job - before_script: - - composer install - cache: - paths: - - vendor - - $COMPOSER_CACHE_DIR - variables: - COMPOSER_CACHE_DIR: $CI_PROJECT_DIR/._composer-cache - -## -## "Test" stage jobs -## - -codestyle: - extends: .composer-job - stage: test - script: - - composer codestyle -- --no-progress-bar --no-interaction - -phpstan: - extends: .composer-job - stage: test - script: - - composer dump-autoload --classmap-authoritative - - composer lint -- --no-interaction --no-progress - -phpunit: - extends: .composer-job - stage: test - script: - - docker-php-ext-enable xdebug - - composer test -- --no-interaction - - cat build/coverage.txt - coverage: '/^\s*Lines:\s*\d+.\d+\%/' - artifacts: - paths: - - build/coverage/ - reports: - junit: build/report.junit.xml diff --git a/README.md b/README.md index c2fc9c0..6e331aa 100755 --- a/README.md +++ b/README.md @@ -57,7 +57,10 @@ The MIT License (MIT). Please see [License File](LICENSE.md) for more informatio [ico-version]: https://img.shields.io/packagist/v/spaceonfire/container.svg?style=flat-square [ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square [ico-downloads]: https://img.shields.io/packagist/dt/spaceonfire/container.svg?style=flat-square +[ico-coverage]: https://img.shields.io/endpoint?style=flat-square&url=https%3A%2F%2Fgist.githubusercontent.com%2Fhustlahusky%2Fd62607c1a2e4707959b0142e0ea876cd%2Fraw%2Fspaceonfire-container.json +[ico-build-status]: https://github.com/spaceonfire/container/workflows/Build%20Pipeline/badge.svg [link-packagist]: https://packagist.org/packages/spaceonfire/container [link-downloads]: https://packagist.org/packages/spaceonfire/container [link-author]: https://github.com/hustlahusky [link-contributors]: ../../contributors +[link-actions]: ../../actions diff --git a/bin/coverage-badge b/bin/coverage-badge new file mode 100644 index 0000000..c0f37be --- /dev/null +++ b/bin/coverage-badge @@ -0,0 +1,129 @@ +#!/usr/bin/env php + + */ + +try { + $arguments = $argv; + array_shift($arguments); + + [$filename, $gistId, $gistToken] = $arguments + [null, null, null]; + + if ($filename === null) { + throw new InvalidArgumentException('Badge filename not specified'); + } + if ($gistId === null) { + throw new InvalidArgumentException('Gist id not specified'); + } + if ($gistToken === null) { + throw new InvalidArgumentException('Gist token not specified'); + } + + $filename = str_replace(['/', '_'], '-', $filename); + + $coverageFile = dirname(__DIR__) . '/build/coverage.txt'; + + if (!file_exists($coverageFile)) { + throw new RuntimeException(sprintf('Text coverage report not found at %s', $coverageFile)); + } + + $coverageFileContents = file_get_contents($coverageFile); + + preg_match('/^\s+Lines:\s+(\d+\.\d+)\%/m', $coverageFileContents, $matches); + + if (isset($matches[1]) && is_numeric($matches[1])) { + $coverage = (float)$matches[1]; + + /** @noinspection TypeUnsafeComparisonInspection */ + $message = sprintf( + '%s%%', + (string)$coverage == (int)$coverage + ? (string)(int)$coverage + : number_format($coverage, 1) + ); + + $color = 'red'; + + if ($coverage >= 30) { + $color = 'orange'; + } + if ($coverage >= 50) { + $color = 'yellow'; + } + if ($coverage >= 65) { + $color = 'yellowgreen'; + } + if ($coverage >= 80) { + $color = 'green'; + } + if ($coverage >= 95) { + $color = 'brightgreen'; + } + } else { + $message = 'N/A'; + $color = 'lightgrey'; + } + + $badgeInfo = [ + 'schemaVersion' => 1, + 'label' => 'coverage', + 'message' => $message, + 'color' => $color, + ]; + + $postFields = json_encode([ + 'files' => [ + $filename => [ + 'content' => json_encode($badgeInfo), + ], + ], + ]); + + $curl = curl_init(); + + curl_setopt_array($curl, [ + CURLOPT_URL => sprintf('https://api.github.com/gists/%s', $gistId), + CURLOPT_RETURNTRANSFER => true, + CURLOPT_ENCODING => '', + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 30, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => 'PATCH', + CURLOPT_POSTFIELDS => $postFields, + CURLOPT_HTTPHEADER => [ + 'accept: application/vnd.github.v3+json', + sprintf('authorization: Bearer %s', $gistToken), + 'content-type: application/json', + 'user-agent: curl', + ], + ]); + + $response = curl_exec($curl); + $json = json_decode($response, true); + $err = curl_error($curl); + $httpCode = curl_getinfo($curl, CURLINFO_RESPONSE_CODE); + + curl_close($curl); + + if ($err) { + throw new RuntimeException(sprintf('Curl error: %s', $err)); + } + + if ($httpCode > 399) { + $exceptionMessage = sprintf('Request error: %s', $json['message'] ?? $response); + + if (isset($json['errors'])) { + $exceptionMessage .= PHP_EOL . PHP_EOL . 'Errors: ' . json_encode($json['errors'], JSON_PRETTY_PRINT); + } + + throw new RuntimeException($exceptionMessage); + } + + echo 'Coverage badge info uploaded' . PHP_EOL; + exit(0); +} catch (Throwable $e) { + fwrite(STDERR, $e->getMessage() . PHP_EOL); + exit(1); +} diff --git a/composer.json b/composer.json index 5af63e5..7f7268e 100755 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "phpunit/phpunit": "^8.5", "phpstan/phpstan": "^0.12", "roave/security-advisories": "dev-master", - "symplify/easy-coding-standard-prefixed": "^8.0" + "symplify/easy-coding-standard-prefixed": "^8.3" }, "provide": { "psr/container-implementation": "^1.0"