name: CI on: pull_request: push: branches: - main - arch - 'v5.*' - 3.x - 2.x env: FORCE_COLOR: 1 HEAD_COMMIT: ${{ github.sha }} GITHUB_CONTEXT: ${{ toJson(github) }} CACHED_DEPENDENCY_PATHS: | ${{ github.workspace }}/node_modules ${{ github.workspace }}/apps/*/node_modules ${{ github.workspace }}/ghost/*/node_modules ~/.cache/ms-playwright/ CACHED_BUILD_PATHS: | ${{ github.workspace }}/ghost/*/build NX_CACHE_RESTORE_KEYS: | nx-Linux-${{ github.ref }}-${{ github.sha }} nx-Linux-${{ github.ref }} nx-Linux 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 (push) if: github.event_name == 'push' run: | NUMBER_OF_COMMITS=$(printf "%s\n" '${{ toJson(github.event.commits.*.id) }}' | jq length) echo "There are $NUMBER_OF_COMMITS commits in this push." echo "BASE_COMMIT=$(git rev-parse HEAD~$NUMBER_OF_COMMITS)" >> $GITHUB_ENV - name: Get metadata (pull_request) if: github.event_name == 'pull_request' run: | BASE_COMMIT=$(curl --location --request GET 'https://api.github.com/repos/TryGhost/Ghost/pulls/${{ github.event.pull_request.number }}' --header 'Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' | jq -r .base.sha) echo "Setting BASE_COMMIT to $BASE_COMMIT" echo "BASE_COMMIT=$BASE_COMMIT" >> $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/**' 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_any_code: ${{ steps.changed.outputs.any-code }} base_commit: ${{ env.BASE_COMMIT }} is_canary_branch: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/arch') }} is_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: 'Checkout current commit' 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: Nx cache uses: actions/cache@v3 id: cache_nx with: path: .nxcache key: nx-Linux-${{ github.ref }}-${{ env.HEAD_COMMIT }} restore-keys: ${{needs.job_get_metadata.outputs.is_main == 'false' && env.NX_CACHE_RESTORE_KEYS || 'nx-never-restore'}} - 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: Check build cache uses: actions/cache@v3 id: cache_built_packages with: path: ${{ env.CACHED_BUILD_PATHS }} key: ${{ env.HEAD_COMMIT }} - 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 - name: Build packages if: steps.cache_built_packages.outputs.cache-hit != 'true' run: yarn nx run-many -t build:ts 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' 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 nx affected -t lint --base=${{ needs.job_get_metadata.outputs.BASE_COMMIT }} - 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' 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 - name: Merge Admin test coverage 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_unit-tests: runs-on: ubuntu-latest needs: [job_get_metadata, job_install_deps] if: needs.job_get_metadata.outputs.changed_any_code == 'true' strategy: matrix: node: [ '16.14.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 nx affected -t test:unit --base=${{ needs.job_get_metadata.outputs.BASE_COMMIT }} - 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' strategy: matrix: node: [ '16.14.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' 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' 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' 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' 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' 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.14.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 if: contains(needs.job_admin-tests.result, 'success') uses: actions/download-artifact@v3 with: name: admin-coverage - name: Move coverage if: contains(needs.job_admin-tests.result, 'success') run: | rsync -av --remove-source-files admin/* ghost/admin - name: Upload Admin test coverage uses: codecov/codecov-action@v3 with: flags: admin-tests move_coverage_to_trash: true - name: Restore E2E coverage if: contains(needs.job_database-tests.result, 'success') uses: actions/download-artifact@v3 with: name: e2e-coverage - name: Move coverage if: contains(needs.job_database-tests.result, 'success') run: | rsync -av --remove-source-files core/* ghost/core - name: Upload E2E test coverage if: contains(needs.job_database-tests.result, 'success') uses: codecov/codecov-action@v3 with: flags: e2e-tests move_coverage_to_trash: true job_required_tests: name: All required tests passed or skipped needs: [ job_get_metadata, job_install_deps, job_lint, job_ghost-cli, job_admin-tests, 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_get_metadata, job_required_tests ] name: Canary runs-on: ubuntu-latest if: always() && needs.job_get_metadata.outputs.is_canary_branch == 'true' && !(contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped')) steps: - name: Set env variables run: | echo "CANARY_BUILD_INPUTS={\"version\":\"canary\",\"environment\":\"staging\"}" >> $GITHUB_ENV - name: Invoke build uses: aurelien-baudet/workflow-dispatch@v2 with: token: ${{ secrets.CANARY_DOCKER_BUILD }} workflow: .github/workflows/deploy.yml ref: 'refs/heads/main' repo: TryGhost/Ghost-Moya inputs: ${{ env.CANARY_BUILD_INPUTS }} wait-for-completion-timeout: 25m wait-for-completion-interval: 30s