name: CI
run-name: >-
  ${{ (github.event_name == 'push' && github.ref == 'refs/heads/master') &&
         'Deploy to nextstrain-canary'
      || (github.event_name == 'workflow_dispatch' && inputs.heroku-app != 'none') &&
         format('Deploy to {0}', inputs.heroku-app)
      || 'CI' }}

on:
  push:
    branches:
      - master
  pull_request:
  workflow_dispatch:
    inputs:
      heroku-app:
        description: Deploy to Heroku app
        type: choice
        default: 'none'
        options:
          - 'none'
          - 'nextstrain-dev'
          - 'nextstrain-canary'

# Only one run per branch at a time.
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

defaults:
  run:
    # This is the same as GitHub Action's `bash` keyword as of 20 June 2023:
    # https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsshell
    #
    # Completely spelling it out here so that GitHub can't change it out from under us
    # and we don't have to refer to the docs to know the expected behavior.
    shell: bash --noprofile --norc -eo pipefail {0}

jobs:
  # Lint regardless of build or test status and lint early.
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version-file: 'package.json'
      - run: node --version
      - run: npm ci
      - run: npm run lint
      - run: node ./scripts/check-resource-index-match.js

  type-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version-file: 'package.json'
      - run: npm ci
      - run: npm run type-check:ci

  check-dependencies:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version-file: 'package.json'
      - run: npm ci
      - run: npm run check-dependencies

  # Build into Heroku slug so we can deploy the same build that we test.
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7

      - run: ./scripts/heroku-build

      - if: always()
        uses: actions/upload-artifact@v7
        with:
          name: build
          path: |
            build/slug.tar.gz
            build/slug.tar.gz.sha256sum
            build/slug.json

  # Run tests in Heroku runtime environment against our built slug.
  test:
    if: github.repository == 'nextstrain/nextstrain.org'
    needs: build
    runs-on: ubuntu-latest
    container:
      image: heroku/heroku:24
      # The image's default user does not have access to the working directory.
      # <https://devcenter.heroku.com/articles/heroku-24-stack>
      options: --user root
    env:
      # Override NODE_ENV=production default in production build (slug)
      NODE_ENV: development

      # Set up Heroku runtime environment.  See bash(1) or
      # <https://www.gnu.org/software/bash/manual/bash.html#Invoked-non_002dinteractively>.
      BASH_ENV: .heroku/BASH_ENV
    steps:
      - uses: actions/download-artifact@v8
        with:
          name: build
          path: build/
      - run: sha256sum --check build/slug.tar.gz.sha256sum
      - run: tar --extract --file build/slug.tar.gz --xform 's,^[.]/app/,,'
      - run: node --version

      # (Re-)install dev deps which got pruned from production build (slug)
      - run: npm ci

      # configure AWS to run dev server necessary for `npm run test:ci`
      - uses: aws-actions/configure-aws-credentials@v6
        with:
          aws-region: ${{ vars.AWS_DEFAULT_REGION }}
          aws-access-key-id: ${{ secrets.DEV_SERVER_AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.DEV_SERVER_AWS_SECRET_ACCESS_KEY }}

      - run: npm run test:ci
        env:
          # Tests make GitHub API requests which are rate limited.
          # Use the GitHub Actions token for a limit of 1,000 requests per hour.
          # <https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api#primary-rate-limit-for-github_token-in-github-actions>
          GITHUB_TOKEN: ${{ github.token }}

      - if: always()
        uses: actions/upload-artifact@v7
        with:
          name: test-logs
          path: test/server.log

  index-resources:
    if: |2
         github.repository == 'nextstrain/nextstrain.org'
      && github.event_name == 'push'
      && github.ref == 'refs/heads/master'
    needs: test
    permissions:
      id-token: write # needed to interact with GitHub's OIDC Token endpoint
      contents: read
    uses: ./.github/workflows/index-resources.yml
    secrets: inherit

  vars:
    runs-on: ubuntu-latest
    outputs:
      heroku-app: ${{ steps.heroku-app.outputs.value }}
    steps:
      - name: Set heroku-app
        id: heroku-app
        run: |
          # Push to default branch
          if [[ "${{ github.event_name }}" == "push" && \
                "${{ github.ref }}" == "refs/heads/master" ]]; then

            echo "value=nextstrain-canary" | tee -a "$GITHUB_OUTPUT"

          # workflow_dispatch with a selected app
          elif [[ "${{ github.event_name }}" == "workflow_dispatch" && \
                  "${{ inputs.heroku-app }}" != "none" ]]; then

            echo "value=${{ inputs.heroku-app }}" | tee -a "$GITHUB_OUTPUT"

          # No deploy
          else
            echo "value=" | tee -a "$GITHUB_OUTPUT"
          fi

  deploy:
    if: |2
         !cancelled()
      && needs.build.result == 'success'
      && needs.test.result == 'success'
      && contains(fromJSON('["success", "skipped"]'), needs.index-resources.result)
      && github.repository == 'nextstrain/nextstrain.org'
      && needs.vars.outputs.heroku-app != ''

    # Wait for "build", "test", "index-resources", and "vars" jobs above to pass.
    needs:
      - build
      - test
      - index-resources
      - vars

    # Name a GitHub environment configuration¹ to auto-create deployment
    # records in GitHub based on this job's progress/status.  Also grants
    # access to environment-specific secrets.
    #
    # The URL is specific to this deployment, not the environment (i.e. an
    # environment have deployments at different URLs).
    #
    # ¹ https://github.com/nextstrain/nextstrain.org/settings/environments
    environment:
      name: ${{ needs.vars.outputs.heroku-app }}
      url: ${{ needs.vars.outputs.heroku-app && format('https://{0}.herokuapp.com', needs.vars.outputs.heroku-app) || 'https://next.nextstrain.org' }}

    # Deploy steps
    runs-on: ubuntu-latest
    env:
      HEROKU_APP: ${{ needs.vars.outputs.heroku-app }}
    steps:
      - uses: actions/download-artifact@v8
        with:
          name: build
          path: build/

      - name: Login to Heroku
        run: echo "machine api.heroku.com login $HEROKU_USER password $HEROKU_TOKEN" >> ~/.netrc
        env:
          HEROKU_USER: "${{ vars.HEROKU_USER }}"
          HEROKU_TOKEN: "${{ secrets.HEROKU_TOKEN }}"

      - name: Upload slug
        run: |
          # <https://devcenter.heroku.com/articles/platform-api-reference#slug-create>
          curl https://api.heroku.com/apps/"$HEROKU_APP"/slugs \
            --data-binary @build/slug.json \
            --header 'Content-Type: application/json' \
            --header 'Accept: application/vnd.heroku+json; version=3' \
            --fail --silent --show-error --location --netrc \
              | tee slug.json

          curl "$(jq -r .blob.url slug.json)" \
            --request "$(jq -r .blob.method slug.json | tr a-z A-Z)" \
            --header "Content-Type:" \
            --data-binary @build/slug.tar.gz \
            --fail --location --netrc \
              | cat

      - name: Release slug
        run: |
          # <https://devcenter.heroku.com/articles/platform-api-reference#release-create>
          curl https://api.heroku.com/apps/"$HEROKU_APP"/releases \
            --data-binary @<(jq '{slug: .id}' slug.json) \
            --header 'Content-Type: application/json' \
            --header 'Accept: application/vnd.heroku+json; version=3' \
            --fail --silent --show-error --location --netrc \
              | tee release.json

      - if: always()
        uses: actions/upload-artifact@v7
        with:
          name: deploy
          path: |
            slug.json
            release.json

      - if: always()
        name: Logout of Heroku
        run: sed -i -e '/^machine api\.heroku\.com/d' ~/.netrc
