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 if: github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/')) 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 if: github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/')) 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 with: fetch-depth: 100 - 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 --since ${{ github.event.pull_request.base.sha || github.event.before }} - 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_any_code == '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 with: fetch-depth: 100 - 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 --include-dependents --since ${{ github.event.pull_request.base.sha || github.event.before }} - 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@v3 - 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() && (github.event_name == 'push' || (github.event_name == 'pull_request' && !startsWith(github.head_ref, 'renovate/'))) 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