name: "Spec"
on:
  push:
    branches:
      - main
    tags:
      - v*
  pull_request:
  workflow_dispatch:
  schedule:
    # Run this workflow every day at 2am. This helps keep the page of
    # current spec proposals up-to-date.
    - cron: '0 2 * * *'

jobs:
  validate-openapi:
    name: "🔎 Validate OpenAPI specifications"
    runs-on: ubuntu-latest
    steps:
      - name: "📥 Source checkout"
        uses: actions/checkout@v4
      - name: "➕ Setup Node"
        uses: actions/setup-node@v4
        with:
          node-version: '20'
      - name: "🔎 Run validator"
        run: |
          npx @redocly/cli@latest lint data/api/*/*.yaml

  check-event-examples:
    name: "🔎 Check Event schema examples"
    runs-on: ubuntu-latest
    steps:
      - name: "📥 Source checkout"
        uses: actions/checkout@v4
      - name: "➕ Setup Python"
        uses: actions/setup-python@v5
        with:
          python-version: '3.9'
          cache: 'pip'
          cache-dependency-path: scripts/requirements.txt
      - name: "➕ Install dependencies"
        run: |
          pip install -r scripts/requirements.txt
      - name: "🔎 Run validator"
        run: |
          python scripts/check-event-schema-examples.py

  check-openapi-examples:
    name: "🔎 Check OpenAPI definitions examples"
    runs-on: ubuntu-latest
    steps:
      - name: "📥 Source checkout"
        uses: actions/checkout@v4
      - name: "➕ Setup Python"
        uses: actions/setup-python@v5
        with:
          python-version: '3.9'
          cache: 'pip'
          cache-dependency-path: scripts/requirements.txt
      - name: "➕ Install dependencies"
        run: |
          pip install -r scripts/requirements.txt
      - name: "🔎 Run validator"
        run: |
          python scripts/check-openapi-sources.py

  check-schemas-examples:
    name: "🔎 Check JSON Schemas inline examples"
    runs-on: ubuntu-latest
    steps:
      - name: "📥 Source checkout"
        uses: actions/checkout@v4
      - name: "➕ Setup Python"
        uses: actions/setup-python@v5
        with:
          python-version: '3.9'
          cache: 'pip'
          cache-dependency-path: scripts/requirements.txt
      - name: "➕ Install dependencies"
        run: |
          pip install -r scripts/requirements.txt
      - name: "🔎 Run validator"
        run: |
          python scripts/check-json-schemas.py

  calculate-baseurl:
    name: "⚙️ Calculate baseURL for later jobs"
    runs-on: ubuntu-latest
    outputs:
      baseURL: "${{ steps.set-baseurl.outputs.baseURL }}"
    steps:
      # For PRs, set the baseURL to `/`.
      # For releases, set the baseURL to `/$tag` (eg: `/v1.2`).
      # Otherwise, set it to `/unstable`.
      - name: "⚙️ Calculate baseURL"
        id: set-baseurl
        # Double brackets on the elif to avoid auto-escaping refs/tags/* because we need
        # the asterisk matching behaviour, not the literal string.
        run: |
          if [ "${GITHUB_EVENT_NAME}" == "pull_request" ]; then
              echo "baseURL=/" >> "$GITHUB_OUTPUT"
          elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then
              echo "baseURL=/${GITHUB_REF/refs\/tags\//}" >> "$GITHUB_OUTPUT"
          else
              echo "baseURL=/unstable" >> "$GITHUB_OUTPUT"
          fi

  build-openapi:
    name: "🐍 Build OpenAPI definitions"
    runs-on: ubuntu-latest
    needs: [calculate-baseurl]
    steps:
      - name: "📥 Source checkout"
        uses: actions/checkout@v4
      - name: "➕ Setup Python"
        uses: actions/setup-python@v5
        with:
          python-version: '3.9'
          cache: 'pip'
          cache-dependency-path: scripts/requirements.txt
      - name: "➕ Install dependencies"
        run: |
          pip install -r scripts/requirements.txt
      - name: "📦 Asset creation"
        run: |
          if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
            export RELEASE="${GITHUB_REF/refs\/tags\//}"
          else
            export RELEASE="unstable"
          fi
          # The output path matches the final deployment path at spec.matrix.org
          scripts/dump-openapi.py \
            --base-url "https://spec.matrix.org${{ needs.calculate-baseurl.outputs.baseURL }}" \
            --api application-service \
            -r "$RELEASE" \
            -o spec/application-service-api/api.json
          scripts/dump-openapi.py \
            --base-url "https://spec.matrix.org${{ needs.calculate-baseurl.outputs.baseURL }}" \
            --api client-server \
            -r "$RELEASE" \
            -o spec/client-server-api/api.json
          scripts/dump-openapi.py \
            --base-url "https://spec.matrix.org${{ needs.calculate-baseurl.outputs.baseURL }}" \
            --api push-gateway \
            -r "$RELEASE" \
            -o spec/push-gateway-api/api.json
          scripts/dump-openapi.py \
              --base-url "https://spec.matrix.org${{ needs.calculate-baseurl.outputs.baseURL }}" \
              --api server-server \
              -r "$RELEASE" \
              -o spec/server-server-api/api.json
          tar -czf openapi.tar.gz spec
      - name: "📤 Artifact upload"
        uses: actions/upload-artifact@v4
        with:
          name: openapi-artifact
          path: openapi.tar.gz

  generate-changelog:
    name: "📢 Run towncrier for changelog"
    # skip for builds of git tags
    if: "!startsWith(github.ref, 'refs/tags/')"
    runs-on: ubuntu-latest
    steps:
      - name: "📥 Source checkout"
        uses: actions/checkout@v4
      - name: "➕ Setup Python"
        uses: actions/setup-python@v5
        with:
          python-version: '3.9'
      - name: "➕ Install towncrier"
        run: "pip install 'towncrier'"
      - name: "Generate changelog"
        run: ./scripts/generate-changelog.sh vUNSTABLE
      - name: "📤 Artifact upload"
        uses: actions/upload-artifact@v4
        with:
          name: changelog-artifact
          path: content/changelog/vUNSTABLE.md

  build-spec:
    name: "📖 Build the spec"
    runs-on: ubuntu-latest
    needs: [calculate-baseurl, build-openapi, generate-changelog]
    # run even if generate-changelog was skipped
    if: ${{ always() }}
    steps:
      - name: "➕ Setup Node"
        uses: actions/setup-node@v4
        with:
          node-version: '20'
      - name: "➕ Setup Hugo"
        uses: peaceiris/actions-hugo@75d2e84710de30f6ff7268e08f310b60ef14033f # v3.0.0
        with:
          hugo-version: '0.117.0'
          extended: true
      - name: "📥 Source checkout"
        uses: actions/checkout@v4
      - name: "⚙️ npm"
        run: |
          npm i
          npm run get-proposals
      - name: "📥 Download generated changelog"
        if: "needs.generate-changelog.result == 'success'"
        uses: actions/download-artifact@v4
        with:
          name: changelog-artifact
          path: content/changelog
      - name: "⚙️ hugo"
        run: hugo --baseURL "${{ needs.calculate-baseurl.outputs.baseURL }}" -d "spec"
      # We manually unpack the spec OpenAPI definition JSON to the website tree
      # to make it available to the world in a canonical place:
      # https://spec.matrix.org/latest/client-server-api/api.json
      # Works for /unstable/ and /v1.1/ as well.
      - name: "📥 Spec definition download"
        uses: actions/download-artifact@v4
        with:
          name: openapi-artifact
      - name: "📝 Unpack the OpenAPI definitions in the right location"
        run: |
          tar -xzf openapi.tar.gz

      - name: "📦 Tarball creation"
        run: tar -czf spec.tar.gz spec
      - name: "📤 Artifact upload"
        uses: actions/upload-artifact@v4
        with:
          name: spec-artifact
          path: spec.tar.gz

  htmlcheck:
    name: "🔎 Validate generated HTML"
    runs-on: ubuntu-latest
    needs: [calculate-baseurl, build-spec]
    steps:
      - name: "📥 Source checkout"
        uses: actions/checkout@v4

      - name: "📥 Fetch built spec"
        uses: actions/download-artifact@v4
        with:
          name: spec-artifact

      - name: "📝 Unpack the spec"
        # we have to unpack it into the right path given the baseurl, so that the
        # links are correct.
        # eg if baseurl is `/unstable`, we want to put the site in `spec/unstable`.
        run: |
          mkdir -p "spec${baseURL}"
          tar -C "spec${baseURL}" --strip-components=1 -xvzf spec.tar.gz
        env:
          baseURL: "${{ needs.calculate-baseurl.outputs.baseURL }}"

      - name: "Run htmltest"
        uses: wjdp/htmltest-action@master
        with:
          config: .htmltest.yaml

  build-historical-spec:
    name: "📖 Build the historical backup spec"
    runs-on: ubuntu-latest
    needs: [build-openapi]
    if: ${{ startsWith(github.ref, 'refs/tags/') }}
    steps:
      - name: "➕ Setup Node"
        uses: actions/setup-node@v4
        with:
          node-version: '20'
      - name: "➕ Setup Hugo"
        uses: peaceiris/actions-hugo@75d2e84710de30f6ff7268e08f310b60ef14033f # v3.0.0
        with:
          # Cannot build the spec with Hugo 0.125.0 because of https://github.com/google/docsy/issues/1930
          hugo-version: '0.124.1'
          extended: true
      - name: "📥 Source checkout"
        uses: actions/checkout@v4
      - name: "⚙️ npm"
        run: |
          npm i
          npm run get-proposals
      - name: "⚙️ hugo"
        # Create a baseURL like `/v1.2` out of the `v1.2` tag
        run: |
          echo -e '[params.version]\nstatus="historical"' > historical.toml
          hugo --config config.toml,historical.toml --baseURL "/${GITHUB_REF/refs\/tags\//}" -d "spec"

      - name: "📥 Spec definition download"
        uses: actions/download-artifact@v4
        with:
          name: openapi-artifact
      - name: "📝 Unpack the OpenAPI definitions in the right location"
        run: |
          tar -xzf openapi.tar.gz

      - name: "📦 Tarball creation"
        run: tar -czf spec-historical.tar.gz spec
      - name: "📤 Artifact upload"
        uses: actions/upload-artifact@v4
        with:
          name: spec-historical-artifact
          path: spec-historical.tar.gz
