From 88954f7318ee631d4e751c890bc9c49ad7f05c5b Mon Sep 17 00:00:00 2001 From: Daniel Lockyer Date: Mon, 3 Jul 2023 14:53:55 +0200 Subject: [PATCH] Reworked CI workflow to optimize setup refs https://github.com/TryGhost/Toolbox/issues/609 - this rewrites the CI workflow to include a pre-test step which will download and cache dependencies, and will only run tests when the associated code changes - this provides a huge improvement over the existing setup, and will save us a lot of time in CI --- .github/actions/restore-cache/action.yml | 18 + .github/workflows/admin-x-settings-tests.yml | 53 -- .github/workflows/ci.yml | 795 +++++++++++++++++++ .github/workflows/comments-ui-tests.yml | 53 -- .github/workflows/signup-form-tests.yml | 53 -- .github/workflows/test.yml | 467 ----------- README.md | 2 +- 7 files changed, 814 insertions(+), 627 deletions(-) create mode 100644 .github/actions/restore-cache/action.yml delete mode 100644 .github/workflows/admin-x-settings-tests.yml create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/comments-ui-tests.yml delete mode 100644 .github/workflows/signup-form-tests.yml delete mode 100644 .github/workflows/test.yml diff --git a/.github/actions/restore-cache/action.yml b/.github/actions/restore-cache/action.yml new file mode 100644 index 0000000000..0bbd975680 --- /dev/null +++ b/.github/actions/restore-cache/action.yml @@ -0,0 +1,18 @@ +name: "Restore dependency cache" +description: "Restore the dependency cache." + +runs: + using: "composite" + steps: + - name: Check dependency cache + id: dep-cache + uses: actions/cache/restore@v3 + with: + path: ${{ env.CACHED_DEPENDENCY_PATHS }} + key: ${{ env.DEPENDENCY_CACHE_KEY }} + + - name: Check if caches are restored + uses: actions/github-script@v6 + if: steps.dep-cache.outputs.cache-hit != 'true' + with: + script: core.setFailed('Dependency cache could not be restored - please re-run ALL jobs.') \ No newline at end of file diff --git a/.github/workflows/admin-x-settings-tests.yml b/.github/workflows/admin-x-settings-tests.yml deleted file mode 100644 index 1063359f31..0000000000 --- a/.github/workflows/admin-x-settings-tests.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: AdminX Settings Tests -on: - pull_request: - paths: - - 'apps/admin-x-settings/**' - push: - branches: - - main - - 'v5.*' - paths: - - 'apps/admin-x-settings/**' -env: - FORCE_COLOR: 1 -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true -jobs: - e2e: - runs-on: ubuntu-latest - if: github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/')) - name: E2E - env: - CI: true - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - env: - FORCE_COLOR: 0 - with: - node-version: "18.12.1" - cache: yarn - - - run: yarn --prefer-offline - - - name: Install Playwright - run: npx playwright install --with-deps - - - run: yarn workspace @tryghost/admin-x-settings run test:e2e - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v3 - with: - name: playwright-report - path: playwright-report - retention-days: 30 - - - uses: tryghost/actions/actions/slack-build@main - if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' - with: - status: ${{ job.status }} - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..e764dbe520 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,795 @@ +name: CI +on: + pull_request: + push: + branches: + - main + - 'v5.*' + - 3.x + - 2.x + - 'renovate/*' + +env: + FORCE_COLOR: 1 + HEAD_COMMIT: ${{ github.sha }} + CACHED_DEPENDENCY_PATHS: | + ${{ github.workspace }}/node_modules + ${{ github.workspace }}/apps/*/node_modules + ${{ github.workspace }}/ghost/*/node_modules + ${{ github.workspace }}/ghost/*/build + ~/.cache/ms-playwright/ + +concurrency: + group: ${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + job_get_metadata: + name: Metadata + runs-on: ubuntu-latest + permissions: + pull-requests: read + steps: + - name: Checkout current commit + uses: actions/checkout@v3 + with: + ref: ${{ env.HEAD_COMMIT }} + fetch-depth: 2 + + - name: Get metadata + id: get_metadata + run: | + COMMIT_SHA=$(git rev-parse --short ${{ github.event.pull_request.head.sha || github.event.head_commit.id || env.HEAD_COMMIT }}) + echo "COMMIT_SHA=$COMMIT_SHA" >> $GITHUB_ENV + echo "COMMIT_MESSAGE=$(git log -n 1 --pretty=format:%s $COMMIT_SHA)" >> $GITHUB_ENV + + - name: Determine changed packages + uses: AurorNZ/paths-filter@v3.0.1 + id: changed + with: + filters: | + shared: &shared + - '.github/**' + - 'package.json' + - 'yarn.lock' + core: + - *shared + - 'ghost/**' + - '!ghost/admin/**' + admin: + - *shared + - 'ghost/admin/**' + admin-x-settings: + - *shared + - 'apps/admin-x-settings/**' + announcement-bar: + - *shared + - 'apps/announcement-bar/**' + comments-ui: + - *shared + - 'apps/comments-ui/**' + portal: + - *shared + - 'apps/portal/**' + signup-form: + - *shared + - 'apps/signup-form/**' + sodo-search: + - *shared + - 'apps/sodo-search/**' + migrations: + - *shared + - 'ghost/core/core/server/data/migrations/**' + any-code: + - '!**/*.md' + outputs: + changed_admin: ${{ steps.changed.outputs.admin }} + changed_core: ${{ steps.changed.outputs.core }} + changed_admin_x_settings: ${{ steps.changed.outputs.admin-x-settings }} + changed_announcement_bar: ${{ steps.changed.outputs.announcement-bar }} + changed_comments_ui: ${{ steps.changed.outputs.comments-ui }} + changed_portal: ${{ steps.changed.outputs.portal }} + changed_signup_form: ${{ steps.changed.outputs.signup-form }} + changed_sodo_search: ${{ steps.changed.outputs.sodo-search }} + changed_migrations: ${{ steps.changed.outputs.migrations }} + changed_any_code: ${{ steps.changed.outputs.any-code }} + commit_label: '${{ env.COMMIT_SHA }}: ${{ env.COMMIT_MESSAGE }}' + is_git_sync: ${{ github.head_ref == 'main' || github.ref == 'refs/heads/main' }} + + job_install_deps: + name: Install Dependencies + needs: job_get_metadata + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: 'Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})' + uses: actions/checkout@v3 + with: + ref: ${{ env.HEAD_COMMIT }} + + - name: Compute dependency cache key + id: compute_lockfile_hash + run: echo "hash=${{ hashFiles('yarn.lock') }}" >> "$GITHUB_OUTPUT" + + - name: Check dependency cache + uses: actions/cache@v3 + id: cache_dependencies + with: + path: ${{ env.CACHED_DEPENDENCY_PATHS }} + key: ${{ steps.compute_lockfile_hash.outputs.hash }} + + - name: Set up Node + uses: actions/setup-node@v3 + if: steps.cache_dependencies.outputs.cache-hit != 'true' + env: + FORCE_COLOR: 0 + with: + node-version: '18.12.1' + cache: yarn + + - name: Install dependencies + if: steps.cache_dependencies.outputs.cache-hit != 'true' + run: yarn install --prefer-offline --frozen-lockfile + outputs: + dependency_cache_key: ${{ steps.compute_lockfile_hash.outputs.hash }} + + job_lint: + runs-on: ubuntu-latest + needs: [job_get_metadata, job_install_deps] + if: needs.job_get_metadata.outputs.changed_any_code == 'true' && (github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/'))) + name: Lint + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + env: + FORCE_COLOR: 0 + with: + node-version: '18.12.1' + + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_install_deps.outputs.dependency_cache_key }} + + - uses: actions/cache@v3 + with: + path: ghost/**/.eslintcache + key: eslint-cache + + - run: yarn lint + + - uses: tryghost/actions/actions/slack-build@main + if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' + with: + status: ${{ job.status }} + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + + job_admin-tests: + runs-on: ubuntu-latest + needs: [job_get_metadata, job_install_deps] + if: needs.job_get_metadata.outputs.changed_admin == 'true' && (github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/'))) + name: Admin tests - Chrome + env: + MOZ_HEADLESS: 1 + JOBS: 1 + CI: true + COVERAGE: true + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: "18.12.1" + + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_install_deps.outputs.dependency_cache_key }} + + - run: yarn workspace ghost-admin run test + env: + BROWSER: Chrome + + # Merge coverage reports and upload + - run: yarn ember coverage-merge + working-directory: ghost/admin + - uses: actions/upload-artifact@v3 + with: + name: admin-coverage + path: ghost/*/coverage/cobertura-coverage.xml + + - uses: tryghost/actions/actions/slack-build@main + if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' + with: + status: ${{ job.status }} + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + + job_migrations: + runs-on: ubuntu-latest + needs: [job_get_metadata, job_install_deps] + if: needs.job_get_metadata.outputs.changed_migrations == 'true' && (github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/'))) + strategy: + matrix: + env: + - DB: sqlite3 + DB_CLIENT: sqlite3 + - DB: mysql8 + DB_CLIENT: mysql + env: + database__client: ${{ matrix.env.DB_CLIENT }} + name: Migrations checks (${{ matrix.env.DB }}) + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + submodules: true + - uses: actions/setup-node@v3 + env: + FORCE_COLOR: 0 + with: + node-version: '18.12.1' + + - name: Shutdown MySQL + run: sudo service mysql stop + if: matrix.env.DB == 'mysql8' + + - uses: daniellockyer/mysql-action@main + if: matrix.env.DB == 'mysql8' + with: + authentication plugin: 'caching_sha2_password' + mysql version: '8.0' + mysql database: 'ghost_testing' + mysql root password: 'root' + + - name: Set env vars (SQLite) + if: contains(matrix.env.DB, 'sqlite') + run: echo "database__connection__filename=/dev/shm/ghost-test.db" >> $GITHUB_ENV + + - name: Set env vars (MySQL) + if: contains(matrix.env.DB, 'mysql') + run: | + echo "database__connection__host=127.0.0.1" >> $GITHUB_ENV + echo "database__connection__user=root" >> $GITHUB_ENV + echo "database__connection__password=root" >> $GITHUB_ENV + echo "database__connection__database=ghost_testing" >> $GITHUB_ENV + + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_install_deps.outputs.dependency_cache_key }} + + - name: Run Ghost and then kill it + run: yarn workspace ghost run start + env: + GHOST_TESTS_KILL_SERVER_AFTER_BOOT: true + + - run: sqlite3 ${{ env.database__connection__filename }} "DELETE FROM migrations WHERE version LIKE '5.%';" + if: matrix.env.DB == 'sqlite3' + - run: mysql -h127.0.0.1 -uroot -proot ghost_testing -e "DELETE FROM migrations WHERE version LIKE '5.%';" + if: matrix.env.DB == 'mysql8' + + - run: yarn knex-migrator migrate --force + + job_unit-tests: + runs-on: ubuntu-latest + needs: [job_get_metadata, job_install_deps] + if: needs.job_get_metadata.outputs.changed_core == 'true' && (github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/'))) + strategy: + matrix: + node: [ '16.13.0', '18.12.1' ] + name: Unit tests (Node ${{ matrix.node }}) + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + env: + FORCE_COLOR: 0 + with: + node-version: ${{ matrix.node }} + + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_install_deps.outputs.dependency_cache_key }} + + - run: yarn test:unit + + - uses: actions/upload-artifact@v3 + if: startsWith(matrix.node, '18') + with: + name: unit-coverage + path: ghost/*/coverage/cobertura-coverage.xml + + - uses: tryghost/actions/actions/slack-build@main + if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' + with: + status: ${{ job.status }} + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + + job_database-tests: + runs-on: ubuntu-latest + needs: [job_get_metadata, job_install_deps] + if: needs.job_get_metadata.outputs.changed_core == 'true' && (github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/'))) + strategy: + matrix: + node: [ '16.13.0', '18.12.1' ] + env: + - DB: mysql8 + NODE_ENV: testing-mysql + include: + - node: 18.12.1 + env: + DB: sqlite3 + NODE_ENV: testing + env: + DB: ${{ matrix.env.DB }} + NODE_ENV: ${{ matrix.env.NODE_ENV }} + name: Database tests (Node ${{ matrix.node }}, ${{ matrix.env.DB }}) + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + env: + FORCE_COLOR: 0 + with: + node-version: ${{ matrix.node }} + + - name: Shutdown MySQL + run: sudo service mysql stop + if: matrix.env.DB == 'mysql8' + + - uses: daniellockyer/mysql-action@main + if: matrix.env.DB == 'mysql8' + with: + authentication plugin: 'caching_sha2_password' + mysql version: '8.0' + mysql database: 'ghost_testing' + mysql root password: 'root' + + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_install_deps.outputs.dependency_cache_key }} + + - name: Record start time + run: date +%s > ${{ runner.temp }}/startTime # Get start time for test suite + + - name: Set env vars (SQLite) + if: contains(matrix.env.DB, 'sqlite') + run: echo "database__connection__filename=/dev/shm/ghost-test.db" >> $GITHUB_ENV + + - name: Set env vars (MySQL) + if: contains(matrix.env.DB, 'mysql') + run: echo "database__connection__password=root" >> $GITHUB_ENV + + - name: E2E tests + working-directory: ghost/core + run: yarn test:ci:e2e + + - name: Integration tests + working-directory: ghost/core + run: yarn test:ci:integration + + # Get runtime in seconds for test suite + - name: Record test duration + run: | + startTime="$(cat ${{ runner.temp }}/startTime)" + endTime="$(date +%s)" + echo "test_time=$(($endTime-$startTime))" >> $GITHUB_ENV + + - uses: actions/upload-artifact@v3 + if: startsWith(matrix.node, '18') && contains(matrix.env.DB, 'mysql') + with: + name: e2e-coverage + path: | + ghost/*/coverage-e2e/cobertura-coverage.xml + ghost/*/coverage-integration/cobertura-coverage.xml + ghost/*/coverage-regression/cobertura-coverage.xml + + # Continue on error if TailScale service is down + - name: Tailscale Action + timeout-minutes: 2 + continue-on-error: true + if: (github.event_name == 'push' && github.repository_owner == 'TryGhost') || (github.event_name == 'pull_request' && startsWith(github.head_ref, 'TryGhost/')) + uses: tailscale/github-action@v1 + with: + authkey: ${{ secrets.TAILSCALE_AUTHKEY }} + + # Report time taken to metrics service + # Continue on error if previous TailScale step fails + - name: Store test duration + uses: tryghost/action-trigger-metric@main + timeout-minutes: 1 + continue-on-error: true + if: (github.event_name == 'push' && github.repository_owner == 'TryGhost') || (github.event_name == 'pull_request' && startsWith(github.head_ref, 'TryGhost/')) + with: + metricName: 'test-time' + metricValue: ${{ env.test_time }} + configuration: | + { + "metrics": { + "transports": ["elasticsearch"], + "metadata": { + "database": "${{ matrix.env.DB }}", + "node": "${{ matrix.node }}" + } + }, + "elasticsearch": { + "host": "${{ secrets.ELASTICSEARCH_HOST }}", + "username": "${{ secrets.ELASTICSEARCH_USERNAME }}", + "password": "${{ secrets.ELASTICSEARCH_PASSWORD }}" + } + } + + - uses: tryghost/actions/actions/slack-build@main + if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' + with: + status: ${{ job.status }} + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + + job_regression-tests: + runs-on: ubuntu-latest + needs: [job_get_metadata, job_install_deps] + if: needs.job_get_metadata.outputs.changed_core == 'true' && (github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/'))) + strategy: + matrix: + include: + - node: 18.12.1 + env: + DB: mysql8 + NODE_ENV: testing-mysql + - node: 18.12.1 + env: + DB: sqlite3 + NODE_ENV: testing + env: + DB: ${{ matrix.env.DB }} + NODE_ENV: ${{ matrix.env.NODE_ENV }} + name: Regression tests (Node ${{ matrix.node }}, ${{ matrix.env.DB }}) + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + env: + FORCE_COLOR: 0 + with: + node-version: ${{ matrix.node }} + + - name: Shutdown MySQL + run: sudo service mysql stop + if: matrix.env.DB == 'mysql8' + + - uses: daniellockyer/mysql-action@main + if: matrix.env.DB == 'mysql8' + with: + authentication plugin: 'caching_sha2_password' + mysql version: '8.0' + mysql database: 'ghost_testing' + mysql root password: 'root' + + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_install_deps.outputs.dependency_cache_key }} + + - name: Set env vars (SQLite) + if: contains(matrix.env.DB, 'sqlite') + run: echo "database__connection__filename=/dev/shm/ghost-test.db" >> $GITHUB_ENV + + - name: Set env vars (MySQL) + if: contains(matrix.env.DB, 'mysql') + run: echo "database__connection__password=root" >> $GITHUB_ENV + + - name: Regression tests + working-directory: ghost/core + run: yarn test:ci:regression + + - uses: tryghost/actions/actions/slack-build@main + if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' + with: + status: ${{ job.status }} + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + + job_admin_x_settings: + runs-on: ubuntu-latest + needs: [job_get_metadata, job_install_deps] + if: needs.job_get_metadata.outputs.changed_admin_x_settings == 'true' && (github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/'))) + name: Admin-X Settings tests + env: + CI: true + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + env: + FORCE_COLOR: 0 + with: + node-version: "18.12.1" + + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_install_deps.outputs.dependency_cache_key }} + + - name: Get Playwright version + id: playwright-version + run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> $GITHUB_OUTPUT + - uses: actions/cache@v3 + name: Check if Playwright browser is cached + id: playwright-cache + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-Playwright-${{steps.playwright-version.outputs.version}} + - name: Install Playwright browser if not cached + if: steps.playwright-cache.outputs.cache-hit != 'true' + run: npx playwright install --with-deps + - name: Install OS dependencies of Playwright if cache hit + if: steps.playwright-cache.outputs.cache-hit == 'true' + run: npx playwright install-deps + + - run: yarn workspace @tryghost/admin-x-settings run test:e2e + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v3 + with: + name: playwright-report + path: playwright-report + retention-days: 30 + + - uses: tryghost/actions/actions/slack-build@main + if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' + with: + status: ${{ job.status }} + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + + job_comments_ui: + runs-on: ubuntu-latest + needs: [job_get_metadata, job_install_deps] + if: needs.job_get_metadata.outputs.changed_comments_ui == 'true' && (github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/'))) + name: Comments-UI tests + env: + CI: true + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + env: + FORCE_COLOR: 0 + with: + node-version: "18.12.1" + + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_install_deps.outputs.dependency_cache_key }} + + - name: Get Playwright version + id: playwright-version + run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> $GITHUB_OUTPUT + - uses: actions/cache@v3 + name: Check if Playwright browser is cached + id: playwright-cache + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-Playwright-${{steps.playwright-version.outputs.version}} + - name: Install Playwright browser if not cached + if: steps.playwright-cache.outputs.cache-hit != 'true' + run: npx playwright install --with-deps + - name: Install OS dependencies of Playwright if cache hit + if: steps.playwright-cache.outputs.cache-hit == 'true' + run: npx playwright install-deps + + - run: yarn workspace @tryghost/comments-ui run test + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v3 + with: + name: playwright-report + path: playwright-report + retention-days: 30 + + - uses: tryghost/actions/actions/slack-build@main + if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' + with: + status: ${{ job.status }} + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + + job_signup_form: + runs-on: ubuntu-latest + needs: [job_get_metadata, job_install_deps] + if: needs.job_get_metadata.outputs.changed_signup_form == 'true' && (github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/'))) + name: Signup-form tests + env: + CI: true + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + env: + FORCE_COLOR: 0 + with: + node-version: "18.12.1" + + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_install_deps.outputs.dependency_cache_key }} + + - name: Get Playwright version + id: playwright-version + run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> $GITHUB_OUTPUT + - uses: actions/cache@v3 + name: Check if Playwright browser is cached + id: playwright-cache + with: + path: ~/.cache/ms-playwright + key: ${{ runner.os }}-Playwright-${{steps.playwright-version.outputs.version}} + - name: Install Playwright browser if not cached + if: steps.playwright-cache.outputs.cache-hit != 'true' + run: npx playwright install --with-deps + - name: Install OS dependencies of Playwright if cache hit + if: steps.playwright-cache.outputs.cache-hit == 'true' + run: npx playwright install-deps + + - run: yarn workspace @tryghost/signup-form run test:e2e + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v3 + with: + name: playwright-report + path: playwright-report + retention-days: 30 + + - uses: tryghost/actions/actions/slack-build@main + if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' + with: + status: ${{ job.status }} + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + + job_ghost-cli: + name: Ghost-CLI tests + needs: [job_get_metadata, job_install_deps] + if: needs.job_get_metadata.outputs.changed_core == 'true' && (github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/'))) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + submodules: true + - uses: actions/setup-node@v3 + env: + FORCE_COLOR: 0 + with: + node-version: '16.13.0' + + - name: Install Ghost-CLI + run: npm install -g ghost-cli@latest + + - name: Restore caches + uses: ./.github/actions/restore-cache + env: + DEPENDENCY_CACHE_KEY: ${{ needs.job_install_deps.outputs.dependency_cache_key }} + + - run: npm --no-git-tag-version version minor # We need to artificially bump the minor version to get migrations to run + working-directory: ghost/core + + - run: npm pack + working-directory: ghost/core + + - run: mv ghost-*.tgz ghost.tgz + working-directory: ghost/core + + - name: Clean Install + run: | + DIR=$(mktemp -d) + ghost install local -d $DIR --archive $(pwd)/ghost/core/ghost.tgz + + - name: Latest Release + run: | + DIR=$(mktemp -d) + ghost install local -d $DIR + ghost update -d $DIR --archive $(pwd)/ghost/core/ghost.tgz + + - name: Update from latest v4 + run: | + DIR=$(mktemp -d) + ghost install v4 --local -d $DIR + ghost update -f -d $DIR --archive $(pwd)/ghost/core/ghost.tgz + + - name: Print debug logs + if: failure() + run: | + [ -f ~/.ghost/logs/*.log ] && cat ~/.ghost/logs/*.log + + - uses: tryghost/actions/actions/slack-build@main + if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' + with: + status: ${{ job.status }} + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + + job_coverage: + name: Coverage + needs: [ + job_admin-tests, + job_database-tests, + job_unit-tests + ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Restore Admin coverage + uses: actions/download-artifact@v3 + with: + name: admin-coverage + - name: Restore E2E coverage + uses: actions/download-artifact@v3 + with: + name: e2e-coverage + + - name: Move coverage + run: | + rsync -av --remove-source-files admin/* ghost/admin + rsync -av --remove-source-files core/* ghost/core + + - name: Upload E2E test coverage + uses: codecov/codecov-action@v3 + with: + flags: e2e-tests + move_coverage_to_trash: true + + - name: Restore Unit test coverage + uses: actions/download-artifact@v3 + with: + name: unit-coverage + path: coverage + - run: rsync -av --remove-source-files coverage/* ghost/ + - name: Upload unit test coverage + uses: codecov/codecov-action@v3 + with: + flags: unit-tests + + job_required_tests: + name: All required tests passed or skipped + needs: + [ + job_install_deps, + job_lint, + job_ghost-cli, + job_admin-tests, + job_migrations, + job_unit-tests, + job_database-tests, + job_regression-tests, + job_admin_x_settings, + job_comments_ui, + job_signup_form, + ] + if: always() + runs-on: ubuntu-latest + steps: + - name: Check for failures + if: contains(needs.*.result, 'failure') + run: | + echo "One of the dependent jobs have failed. You may need to re-run it." && exit 1 + + canary: + needs: + [ + job_lint, + job_ghost-cli, + job_admin-tests, + job_migrations, + job_unit-tests, + job_database-tests, + job_regression-tests + ] + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + name: Canary + secrets: inherit + uses: tryghost/actions/.github/workflows/canary.yml@main diff --git a/.github/workflows/comments-ui-tests.yml b/.github/workflows/comments-ui-tests.yml deleted file mode 100644 index f649f9e59f..0000000000 --- a/.github/workflows/comments-ui-tests.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Comments-UI Tests -on: - pull_request: - paths: - - 'apps/comments-ui/**' - push: - branches: - - main - - 'v5.*' - paths: - - 'apps/comments-ui/**' -env: - FORCE_COLOR: 1 -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true -jobs: - e2e: - runs-on: ubuntu-latest - if: github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/')) - name: Tests - env: - CI: true - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - env: - FORCE_COLOR: 0 - with: - node-version: "18.12.1" - cache: yarn - - - run: yarn --prefer-offline - - - name: Install Playwright - run: npx playwright install --with-deps - - - run: yarn workspace @tryghost/comments-ui run test - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v3 - with: - name: playwright-report - path: playwright-report - retention-days: 30 - - - uses: tryghost/actions/actions/slack-build@main - if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' - with: - status: ${{ job.status }} - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/.github/workflows/signup-form-tests.yml b/.github/workflows/signup-form-tests.yml deleted file mode 100644 index a1de7fc844..0000000000 --- a/.github/workflows/signup-form-tests.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Signup Form Tests -on: - pull_request: - paths: - - 'apps/signup-form/**' - push: - branches: - - main - - 'v5.*' - paths: - - 'apps/signup-form/**' -env: - FORCE_COLOR: 1 -concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} - cancel-in-progress: true -jobs: - e2e: - runs-on: ubuntu-latest - if: github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/')) - name: E2E - env: - CI: true - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - env: - FORCE_COLOR: 0 - with: - node-version: "18.12.1" - cache: yarn - - - run: yarn --prefer-offline - - - name: Install Playwright - run: npx playwright install --with-deps - - - run: yarn workspace @tryghost/signup-form run test:e2e - - - name: Upload test results - if: always() - uses: actions/upload-artifact@v3 - with: - name: playwright-report - path: playwright-report - retention-days: 30 - - - uses: tryghost/actions/actions/slack-build@main - if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' - with: - status: ${{ job.status }} - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 26a07da234..0000000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,467 +0,0 @@ -name: Test Suite -on: - pull_request: - push: - branches: - - main - - 'v5.*' - - 3.x - - 2.x - - 'renovate/*' -env: - FORCE_COLOR: 1 -concurrency: - group: ${{ github.head_ref || github.run_id }} - cancel-in-progress: true -jobs: - lint: - runs-on: ubuntu-latest - if: github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/')) - name: Lint - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - env: - FORCE_COLOR: 0 - with: - node-version: '18.12.1' - cache: yarn - - - uses: actions/cache@v3 - with: - path: ghost/**/.eslintcache - key: eslint-cache - - - run: yarn --prefer-offline - - run: yarn lint - - uses: tryghost/actions/actions/slack-build@main - if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' - with: - status: ${{ job.status }} - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - - admin-tests: - runs-on: ubuntu-latest - if: github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/')) - name: Admin - Chrome - env: - MOZ_HEADLESS: 1 - JOBS: 1 - CI: true - COVERAGE: true - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: "18.12.1" - - - run: yarn --prefer-offline - - run: yarn workspace ghost-admin run test - env: - BROWSER: Chrome - - # Merge coverage reports and upload - - run: yarn ember coverage-merge - working-directory: ghost/admin - - uses: actions/upload-artifact@v3 - with: - name: admin-coverage - path: ghost/*/coverage/cobertura-coverage.xml - - - uses: tryghost/actions/actions/slack-build@main - if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' - with: - status: ${{ job.status }} - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - - migrations: - runs-on: ubuntu-latest - if: github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/')) - strategy: - matrix: - env: - - DB: sqlite3 - DB_CLIENT: sqlite3 - - DB: mysql8 - DB_CLIENT: mysql - env: - database__client: ${{ matrix.env.DB_CLIENT }} - name: Migrations (${{ matrix.env.DB }}) - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - submodules: true - - uses: actions/setup-node@v3 - env: - FORCE_COLOR: 0 - with: - node-version: '18.12.1' - cache: yarn - - - name: Shutdown MySQL - run: sudo service mysql stop - if: matrix.env.DB == 'mysql8' - - - uses: daniellockyer/mysql-action@main - if: matrix.env.DB == 'mysql8' - with: - authentication plugin: 'caching_sha2_password' - mysql version: '8.0' - mysql database: 'ghost_testing' - mysql root password: 'root' - - - name: Set env vars (SQLite) - if: contains(matrix.env.DB, 'sqlite') - run: echo "database__connection__filename=/dev/shm/ghost-test.db" >> $GITHUB_ENV - - - name: Set env vars (MySQL) - if: contains(matrix.env.DB, 'mysql') - run: | - echo "database__connection__host=127.0.0.1" >> $GITHUB_ENV - echo "database__connection__user=root" >> $GITHUB_ENV - echo "database__connection__password=root" >> $GITHUB_ENV - echo "database__connection__database=ghost_testing" >> $GITHUB_ENV - - - run: yarn --prefer-offline - - - name: Run Ghost and then kill it - run: yarn workspace ghost run start - env: - GHOST_TESTS_KILL_SERVER_AFTER_BOOT: true - - - run: sqlite3 ${{ env.database__connection__filename }} "DELETE FROM migrations WHERE version LIKE '5.%';" - if: matrix.env.DB == 'sqlite3' - - run: mysql -h127.0.0.1 -uroot -proot ghost_testing -e "DELETE FROM migrations WHERE version LIKE '5.%';" - if: matrix.env.DB == 'mysql8' - - - run: yarn knex-migrator migrate --force - - unit-tests: - runs-on: ubuntu-latest - if: github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/')) - strategy: - matrix: - node: [ '16.13.0', '18.12.1' ] - name: Unit Tests (Node ${{ matrix.node }}) - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - env: - FORCE_COLOR: 0 - with: - node-version: ${{ matrix.node }} - cache: yarn - - - run: yarn --prefer-offline - - run: yarn test:unit - - - uses: actions/upload-artifact@v3 - if: startsWith(matrix.node, '18') - with: - name: unit-coverage - path: ghost/*/coverage/cobertura-coverage.xml - - - uses: tryghost/actions/actions/slack-build@main - if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' - with: - status: ${{ job.status }} - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - - database-tests: - runs-on: ubuntu-latest - if: github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/')) - strategy: - matrix: - node: [ '16.13.0', '18.12.1' ] - env: - - DB: mysql8 - NODE_ENV: testing-mysql - include: - - node: 18.12.1 - env: - DB: sqlite3 - NODE_ENV: testing - env: - DB: ${{ matrix.env.DB }} - NODE_ENV: ${{ matrix.env.NODE_ENV }} - name: Database Tests (Node ${{ matrix.node }}, ${{ matrix.env.DB }}) - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - env: - FORCE_COLOR: 0 - with: - node-version: ${{ matrix.node }} - cache: yarn - - - name: Shutdown MySQL - run: sudo service mysql stop - if: matrix.env.DB == 'mysql8' - - - uses: daniellockyer/mysql-action@main - if: matrix.env.DB == 'mysql8' - with: - authentication plugin: 'caching_sha2_password' - mysql version: '8.0' - mysql database: 'ghost_testing' - mysql root password: 'root' - - - name: Install dependencies - run: yarn --prefer-offline - - - name: Record start time - run: date +%s > ${{ runner.temp }}/startTime # Get start time for test suite - - - name: Set env vars (SQLite) - if: contains(matrix.env.DB, 'sqlite') - run: echo "database__connection__filename=/dev/shm/ghost-test.db" >> $GITHUB_ENV - - - name: Set env vars (MySQL) - if: contains(matrix.env.DB, 'mysql') - run: echo "database__connection__password=root" >> $GITHUB_ENV - - - name: E2E tests - working-directory: ghost/core - run: yarn test:ci:e2e - - - name: Integration tests - working-directory: ghost/core - run: yarn test:ci:integration - - # Get runtime in seconds for test suite - - name: Record test duration - run: | - startTime="$(cat ${{ runner.temp }}/startTime)" - endTime="$(date +%s)" - echo "test_time=$(($endTime-$startTime))" >> $GITHUB_ENV - - - uses: actions/upload-artifact@v3 - if: startsWith(matrix.node, '18') && contains(matrix.env.DB, 'mysql') - with: - name: e2e-coverage - path: | - ghost/*/coverage-e2e/cobertura-coverage.xml - ghost/*/coverage-integration/cobertura-coverage.xml - ghost/*/coverage-regression/cobertura-coverage.xml - - # Continue on error if TailScale service is down - - name: Tailscale Action - timeout-minutes: 2 - continue-on-error: true - if: (github.event_name == 'push' && github.repository_owner == 'TryGhost') || (github.event_name == 'pull_request' && startsWith(github.head_ref, 'TryGhost/')) - uses: tailscale/github-action@v1 - with: - authkey: ${{ secrets.TAILSCALE_AUTHKEY }} - - # Report time taken to metrics service - # Continue on error if previous TailScale step fails - - name: Store test duration - uses: tryghost/action-trigger-metric@main - timeout-minutes: 1 - continue-on-error: true - if: (github.event_name == 'push' && github.repository_owner == 'TryGhost') || (github.event_name == 'pull_request' && startsWith(github.head_ref, 'TryGhost/')) - with: - metricName: 'test-time' - metricValue: ${{ env.test_time }} - configuration: | - { - "metrics": { - "transports": ["elasticsearch"], - "metadata": { - "database": "${{ matrix.env.DB }}", - "node": "${{ matrix.node }}" - } - }, - "elasticsearch": { - "host": "${{ secrets.ELASTICSEARCH_HOST }}", - "username": "${{ secrets.ELASTICSEARCH_USERNAME }}", - "password": "${{ secrets.ELASTICSEARCH_PASSWORD }}" - } - } - - - uses: tryghost/actions/actions/slack-build@main - if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' - with: - status: ${{ job.status }} - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - - regression-tests: - runs-on: ubuntu-latest - if: github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/')) - strategy: - matrix: - include: - - node: 18.12.1 - env: - DB: mysql8 - NODE_ENV: testing-mysql - - node: 18.12.1 - env: - DB: sqlite3 - NODE_ENV: testing - env: - DB: ${{ matrix.env.DB }} - NODE_ENV: ${{ matrix.env.NODE_ENV }} - name: Regression Tests (Node ${{ matrix.node }}, ${{ matrix.env.DB }}) - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - env: - FORCE_COLOR: 0 - with: - node-version: ${{ matrix.node }} - cache: yarn - - - name: Shutdown MySQL - run: sudo service mysql stop - if: matrix.env.DB == 'mysql8' - - - uses: daniellockyer/mysql-action@main - if: matrix.env.DB == 'mysql8' - with: - authentication plugin: 'caching_sha2_password' - mysql version: '8.0' - mysql database: 'ghost_testing' - mysql root password: 'root' - - - name: Install dependencies - run: yarn --prefer-offline - - - name: Set env vars (SQLite) - if: contains(matrix.env.DB, 'sqlite') - run: echo "database__connection__filename=/dev/shm/ghost-test.db" >> $GITHUB_ENV - - - name: Set env vars (MySQL) - if: contains(matrix.env.DB, 'mysql') - run: echo "database__connection__password=root" >> $GITHUB_ENV - - - name: Regression tests - working-directory: ghost/core - run: yarn test:ci:regression - - - uses: tryghost/actions/actions/slack-build@main - if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' - with: - status: ${{ job.status }} - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - - ghost-cli: - name: Ghost-CLI - if: github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/')) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - submodules: true - - uses: actions/setup-node@v3 - env: - FORCE_COLOR: 0 - with: - node-version: '16.13.0' - cache: yarn - - - name: Install Ghost-CLI - run: npm install -g ghost-cli@latest - - - run: yarn --prefer-offline - - - run: npm --no-git-tag-version version minor # We need to artificially bump the minor version to get migrations to run - working-directory: ghost/core - - - run: npm pack - working-directory: ghost/core - - - run: mv ghost-*.tgz ghost.tgz - working-directory: ghost/core - - - name: Clean Install - run: | - DIR=$(mktemp -d) - ghost install local -d $DIR --archive $(pwd)/ghost/core/ghost.tgz - - - name: Latest Release - run: | - DIR=$(mktemp -d) - ghost install local -d $DIR - ghost update -d $DIR --archive $(pwd)/ghost/core/ghost.tgz - - - name: Update from latest v4 - run: | - DIR=$(mktemp -d) - ghost install v4 --local -d $DIR - ghost update -f -d $DIR --archive $(pwd)/ghost/core/ghost.tgz - - - name: Print debug logs - if: failure() - run: | - [ -f ~/.ghost/logs/*.log ] && cat ~/.ghost/logs/*.log - - - uses: tryghost/actions/actions/slack-build@main - if: failure() && github.event_name == 'push' && github.ref == 'refs/heads/main' - with: - status: ${{ job.status }} - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - - coverage: - name: Coverage - needs: [unit-tests, admin-tests, database-tests] - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Restore Admin coverage - uses: actions/download-artifact@v3 - with: - name: admin-coverage - - name: Restore E2E coverage - uses: actions/download-artifact@v3 - with: - name: e2e-coverage - - - name: Move coverage - run: | - rsync -av --remove-source-files admin/* ghost/admin - rsync -av --remove-source-files core/* ghost/core - - - name: Upload E2E test coverage - uses: codecov/codecov-action@v3 - with: - flags: e2e-tests - move_coverage_to_trash: true - - - name: Restore Unit test coverage - uses: actions/download-artifact@v3 - with: - name: unit-coverage - path: coverage - - run: rsync -av --remove-source-files coverage/* ghost/ - - name: Upload unit test coverage - uses: codecov/codecov-action@v3 - with: - flags: unit-tests - - check: - name: Allow Pull Request auto-merge - if: always() && github.event_name == 'pull_request' - needs: [lint, ghost-cli, admin-tests, migrations, unit-tests, database-tests, regression-tests] - runs-on: ubuntu-latest - steps: - - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@release/v1 - with: - jobs: ${{ toJSON(needs) }} - - canary: - needs: [lint, ghost-cli, admin-tests, migrations, unit-tests, database-tests, regression-tests] - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - name: Canary - secrets: inherit - uses: tryghost/actions/.github/workflows/canary.yml@main diff --git a/README.md b/README.md index a3c597ad04..0824fcf6b3 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Latest release - Build status + Build status Contributors