CI

name: CI

on:
  pull_request:
  workflow_dispatch:
    inputs:
      run_all_tests:
        description: 'Run all extended MAPDL build tests'
        required: true
        type: boolean

  push:
    tags:
      - "v*"
    branches:
       - main
  schedule:
    # * is a special character in YAML so you have to quote this string
    - cron:  '30 4 * * *'

env:
  PROJECT_NAME: 'PyMAPDL'
  MAIN_PYTHON_VERSION: '3.10'
  PACKAGE_NAME: 'ansys-mapdl-core'
  PACKAGE_NAMESPACE: 'ansys.mapdl.core'
  DOCUMENTATION_CNAME: 'mapdl.docs.pyansys.com'
  MEILISEARCH_API_KEY: ${{ secrets.MEILISEARCH_API_KEY }}
  MEILISEARCH_PUBLIC_API_KEY: ${{ secrets.MEILISEARCH_PUBLIC_API_KEY }}
  PYANSYS_OFF_SCREEN: True
  DPF_START_SERVER: False
  DPF_PORT: 21004
  MAPDL_PACKAGE: ghcr.io/ansys/mapdl
  MAPDL_IMAGE_VERSION_DOCS_BUILD: v24.1-ubuntu-student
  ON_CI: True
  PYTEST_ARGUMENTS: '-vvv --durations=10 --maxfail=3 --reruns 3 --reruns-delay 4 --cov=ansys.mapdl.core --cov-report=html'

  # Following env vars when changed will "reset" the mentioned cache,
  # by changing the cache file name. It is rendered as ...-v%RESET_XXX%-...
  # You should go up in number, if you go down (or repeat a previous value)
  # you might end up reusing a previous cache if it haven't been deleted already.
  # It applies 7 days retention policy by default.
  RESET_EXAMPLES_CACHE: 2
  RESET_DOC_BUILD_CACHE: 2
  RESET_AUTOSUMMARY_CACHE: 2

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

defaults:
  run:
    shell: bash

permissions:
  contents: write
  packages: read
  pull-requests: write
  issues: write

jobs:

  update-changelog:
    name: "Update CHANGELOG (on release)"
    if: github.event_name == 'push' && contains(github.ref, 'refs/tags')
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    steps:
      - uses: ansys/actions/doc-deploy-changelog@v6
        with:
          token: ${{ secrets.PYANSYS_CI_BOT_TOKEN }}

  branch-name:
    # Only if the event is a pull request and the branch name is not from the pre-commit-ci bot
    if: github.event_name == 'pull_request' && github.head_ref != 'pre-commit-ci-update-config'
    name: Check the name of the branch
    runs-on: ubuntu-latest
    steps:
      - name: Check branch name
        uses: ansys/actions/branch-name-style@v6

  commit-name:
    if: github.event_name == 'pull_request'
    name: Check the name of the commit
    runs-on: ubuntu-latest
    steps:
      - name: Check commit name
        uses: ansys/actions/commit-style@v6
        with:
          token: ${{ secrets.GITHUB_TOKEN }}

  doc-style:
    name: "Documentation style check"
    runs-on: ubuntu-latest
    steps:
      - name: "Ansys documentation style checks"
        uses: ansys/actions/doc-style@v6
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          vale-version: "3.4.1"

  smoke-tests:
    name: "Build and smoke test (${{ matrix.os }} | Python ${{ matrix.python-version }}) (Release=${{ matrix.should-release }})"
    runs-on: ${{ matrix.os }}
    if: github.ref != 'refs/heads/main'
    timeout-minutes: 20
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        python-version: ['3.9', '3.10', '3.11', '3.12']
        # Only perform wheelhouse builds for macOS when releasing
        should-release:
          - ${{ github.event_name == 'push' && contains(github.ref, 'refs/tags') }}
        exclude:
          - should-release: false
            os: macos-latest
    steps:
      - name: "Build wheelhouse and perform smoke test"
        uses: ansys/actions/build-wheelhouse@v6
        with:
          library-name: ${{ env.PACKAGE_NAME }}
          operating-system: ${{ matrix.os }}
          python-version: ${{ matrix.python-version }}

      - name: "Importing library"
        run: |
          python -c "from ansys.mapdl import core as pymapdl; print(pymapdl.Report())"

      - name: "Checking plotting support"
        run:
          python -c "from pyvista.plotting import system_supports_plotting; print('System support plotting ' + str(system_supports_plotting()))"

  docs-build:
    name: "Build documentation"
    runs-on: ubuntu-latest
    needs: doc-style
    timeout-minutes: 60
    outputs:
      PYMAPDL_VERSION: ${{ steps.version.outputs.PYMAPDL_VERSION }}
    env:
      PYMAPDL_PORT: 21000  # default won't work on GitHub runners
      PYMAPDL_DB_PORT: 21001  # default won't work on GitHub runners
      PYMAPDL_START_INSTANCE: FALSE
      ON_DOCUMENTATION: TRUE
    steps:
      - name: "Install Git and checkout project"
        uses: actions/checkout@v4.1.7

      - name: "Login in Github container registry"
        uses: docker/login-action@v3.2.0
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: "Pull, launch, and validate MAPDL service"
        id: start_mapdl
        env:
          LICENSE_SERVER: ${{ secrets.LICENSE_SERVER }}
          MAPDL_VERSION: ${{ env.MAPDL_IMAGE_VERSION_DOCS_BUILD }}
          DISTRIBUTED_MODE: "dmp"
        run: |
          export INSTANCE_NAME=MAPDL_0
          .ci/start_mapdl.sh &> mapdl_launch.log & export DOCKER_PID=$!
          echo "Launching MAPDL service at PID: $DOCKER_PID"
          echo "DOCKER_PID=$(echo $DOCKER_PID)" >> $GITHUB_OUTPUT

      - name: "DPF server activation"
        run: |
          $(docker pull ghcr.io/ansys/dpf-core:22.2dev && docker run -d --name dpfserver -p ${{ env.DPF_PORT }}:50052 ghcr.io/ansys/dpf-core:22.2dev && echo "DPF Server active on port ${{ env.DPF_PORT }}.") &

      - name: "Getting files change filters"
        uses: dorny/paths-filter@v3
        id: changes
        with:
          filters: |
            workflows:
              - '.github/workflows/**'
              - 'pyproject.toml'
            examples:
              - 'examples/**'
              - 'pyproject.toml'

      - name: "Setup Python with cache"
        uses: actions/setup-python@v5
        if:  steps.changes.outputs.workflows != 'true'
        with:
          cache: 'pip'
          python-version: ${{ env.MAIN_PYTHON_VERSION }}

      - name: "Setup Python without cache"
        uses: actions/setup-python@v5
        if: steps.changes.outputs.workflows == 'true'
        with:
          python-version: ${{ env.MAIN_PYTHON_VERSION }}

      - name: "Install OS packages"
        run: |
          sudo apt update
          sudo apt install zip pandoc libgl1-mesa-glx xvfb texlive-latex-extra latexmk graphviz texlive-xetex texlive-fonts-extra qpdf xindy

      - name: "Test virtual framebuffer"
        run: |
          pip install -r .ci/requirements_test_xvfb.txt
          xvfb-run python .ci/display_test.py

      - name: "Install ansys-mapdl-core"
        run: |
          pip install .
          xvfb-run python -c "from ansys.mapdl import core as pymapdl; print(pymapdl.Report())"

      - name: "Retrieve PyMAPDL version"
        id: version
        run: |
          echo "PYMAPDL_VERSION=$(python -c 'from ansys.mapdl.core import __version__; print(__version__)')" >> $GITHUB_OUTPUT
          echo "PyMAPDL version is: $(python -c "from ansys.mapdl.core import __version__; print(__version__)")"

      - name: "Cache examples"
        uses: actions/cache@v4
        if: steps.changes.outputs.examples != 'true' || (github.ref == 'refs/heads/main' && !contains(github.ref, 'refs/tags'))
        with:
          path: doc/source/examples
          key: Examples-v${{ env.RESET_EXAMPLES_CACHE }}-${{ steps.version.outputs.PYMAPDL_VERSION }}-${{ github.sha }}
          restore-keys: |
            Examples-v${{ env.RESET_EXAMPLES_CACHE }}-${{ steps.version.outputs.PYMAPDL_VERSION }}

      - name: "Cache docs build directory"
        uses: actions/cache@v4
        if: steps.changes.outputs.workflows != 'true' || (github.ref == 'refs/heads/main' && !contains(github.ref, 'refs/tags'))
        with:
          path: doc/_build
          key: doc-build-v${{ env.RESET_DOC_BUILD_CACHE }}-${{ steps.version.outputs.PYMAPDL_VERSION }}-${{ github.sha }}
          restore-keys: |
            doc-build-v${{ env.RESET_DOC_BUILD_CACHE }}-${{ steps.version.outputs.PYMAPDL_VERSION }}

      - name: "Cache autosummary"
        uses: actions/cache@v4
        if: steps.changes.outputs.workflows != 'true' || (github.ref == 'refs/heads/main' && !contains(github.ref, 'refs/tags'))
        with:
          path: doc/source/**/_autosummary/*.rst
          key: autosummary-v${{ env.RESET_AUTOSUMMARY_CACHE }}-${{ steps.version.outputs.PYMAPDL_VERSION }}-${{ github.sha }}
          restore-keys: |
            autosummary-v${{ env.RESET_AUTOSUMMARY_CACHE }}-${{ steps.version.outputs.PYMAPDL_VERSION }}

      - name: "Install docs build requirements"
        run: |
          pip install .[doc]

      - name: "Waiting for the services to be up"
        run: |
          .ci/waiting_services.sh

      - name: "Build documentation"
        run: |
          xvfb-run make -C doc html SPHINXOPTS="-j auto -W --keep-going"

      - name: "Substitute defective GIF"
        run: |
          .ci/substitute_defective_gif.sh

      - name: "Upload HTML Documentation"
        uses: actions/upload-artifact@v4
        with:
          name: documentation-html
          path: doc/_build/html
          retention-days: 7

      - name: "Build PDF Documentation"
        working-directory: doc
        run: make pdf

      - name: "Show latex dir"
        working-directory: doc
        run: ls _build/latex

      - name: "Upload PDF documentation"
        uses: actions/upload-artifact@v4
        with:
          name: documentation-pdf
          path: doc/_build/latex/pymapdl*.pdf
          retention-days: 7

      - name: "Upload minimal requirements file"
        # To include it in the release
        uses: actions/upload-artifact@v4
        with:
          name: minimum_requirements.txt
          path: ./minimum_requirements.txt

      - name: "Collect logs on failure"
        if: always()
        env:
          MAPDL_VERSION: ${{ env.MAPDL_IMAGE_VERSION_DOCS_BUILD }}
          MAPDL_INSTANCE: MAPDL_0
          LOG_NAMES: logs-build-docs
        run: |
          .ci/collect_mapdl_logs.sh

      - name: "Upload logs to GitHub"
        if: always()
        uses: actions/upload-artifact@master
        with:
          name: logs-build-docs.tgz
          path: ./logs-build-docs.tgz

      - name: "Display files structure"
        if: always()
        env:
          MAPDL_INSTANCE: mapdl
          LOG_NAMES: logs-build-docs
        run: |
          .ci/display_logs.sh

  build-test:
    name: "Remote: Build & test MAPDL ${{ matrix.mapdl-version }} (Extended testing ${{ matrix.extended_testing }})"
    runs-on: ubuntu-latest
    if: github.ref != 'refs/heads/main' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
    needs: [smoke-tests]
    timeout-minutes: 35
    strategy:
      fail-fast: false
      matrix:
        mapdl-version: [
          'v22.2.1',
          'v22.2-ubuntu',
          'v23.1.0',
          'v23.1-ubuntu',
          'v23.2.0',
          'v23.2-ubuntu',
          'v24.1.0',
          'v24.1-ubuntu',
          'v24.1-ubuntu-student',
          'v24.2.0',
          'latest-ubuntu',
          'latest-ubuntu-student',
          ]
        extended_testing:
          - ${{ github.event_name == 'schedule' || ( github.event_name == 'workflow_dispatch' && inputs.run_all_tests ) || ( github.event_name == 'push' && contains(github.ref, 'refs/tags') ) }}
        exclude:
          # In PRs skipping all MAPDL version except student ones (commented out).
          - extended_testing: false
            mapdl-version: 'v22.2.1'
          - extended_testing: false
            mapdl-version: 'v22.2-ubuntu'
          - extended_testing: false
            mapdl-version: 'v23.1.0'
          - extended_testing: false
            mapdl-version: 'v23.1-ubuntu'
          - extended_testing: false
            mapdl-version: 'v23.2.0'
          - extended_testing: false
            mapdl-version: 'v23.2-ubuntu'
          - extended_testing: false
            mapdl-version: 'v24.1.0'
          - extended_testing: false
            mapdl-version: 'v24.1-ubuntu'
          # - extended_testing: false
          #   mapdl-version: 'v24.1-ubuntu-student'
          - extended_testing: false
            mapdl-version: 'v24.2.0'
          - extended_testing: false
            mapdl-version: 'latest-ubuntu'
          # - extended_testing: false
          #   mapdl-version: 'latest-ubuntu-student'

    env:
      PYMAPDL_PORT: 21000  # default won't work on GitHub runners
      PYMAPDL_PORT2: 21001  # for the pool testing and default won't work on GitHub runners
      PYMAPDL_DB_PORT: 21002  # default won't work on GitHub runners
      PYMAPDL_DB_PORT2: 21003  # default won't work on GitHub runners
      PYMAPDL_START_INSTANCE: FALSE
      ON_LOCAL: FALSE
      ON_UBUNTU: FALSE

    outputs:
      DISTRIBUTED_MODE: ${{ steps.distributed_mode.outputs.distributed_mode }}

    steps:
      - name: "Install Git and checkout project"
        uses: actions/checkout@v4.1.7

      - name: "Login in Github container registry"
        uses: docker/login-action@v3.2.0
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: "Getting SMP/DMP mode"
        id: distributed_mode
        run: |
          image=${{ matrix.mapdl-version }}
          export distributed_mode="smp"
          if [[ $image == *".1."* ]]; then
            export distributed_mode="dmp";
          fi
          echo "Distributed mode: $distributed_mode"
          echo "distributed_mode=$(echo $distributed_mode)" >> $GITHUB_OUTPUT

      - name: "Pull, launch, and validate MAPDL service"
        id: start_mapdl
        env:
          LICENSE_SERVER: ${{ secrets.LICENSE_SERVER }}
          MAPDL_VERSION: ${{ matrix.mapdl-version }}
          DISTRIBUTED_MODE: ${{ steps.distributed_mode.outputs.distributed_mode }}
        run: |
          echo "Launching first MAPDL instance..."
          export INSTANCE_NAME=MAPDL_0
          .ci/start_mapdl.sh &> mapdl_launch_0.log & export DOCKER_PID_0=$!
          echo "Launching a second instance for MAPDL pool testing..."
          export PYMAPDL_PORT=${{ env.PYMAPDL_PORT2 }}
          export PYMAPDL_DB_PORT=${{ env.PYMAPDL_DB_PORT2 }}
          export INSTANCE_NAME=MAPDL_1
          .ci/start_mapdl.sh &> mapdl_launch_1.log & export DOCKER_PID_1=$!
          echo "Launching MAPDL service 0 at PID: $DOCKER_PID_0"
          echo "Launching MAPDL service 1 at PID: $DOCKER_PID_2"
          echo "DOCKER_PID_0=$(echo $DOCKER_PID_0)" >> $GITHUB_OUTPUT
          echo "DOCKER_PID_1=$(echo $DOCKER_PID_1)" >> $GITHUB_OUTPUT

      - name: "DPF server activation"
        run: |
          $(docker pull ghcr.io/ansys/dpf-core:22.2dev && docker run -d --name dpfserver -p ${{ env.DPF_PORT }}:50052 ghcr.io/ansys/dpf-core:22.2dev && echo "DPF Server active on port ${{ env.DPF_PORT }}.") &

      - name: "Getting files change filters"
        uses: dorny/paths-filter@v3
        id: changes
        with:
          filters: |
            workflows:
              - '.github/workflows/**'

      - name: "Setup Python with cache"
        uses: actions/setup-python@v5
        if:  steps.changes.outputs.workflows != 'true'
        with:
          cache: 'pip'
          python-version: ${{ env.MAIN_PYTHON_VERSION }}

      - name: "Setup Python without cache"
        uses: actions/setup-python@v5
        if: steps.changes.outputs.workflows == 'true'
        with:
          python-version: ${{ env.MAIN_PYTHON_VERSION }}

      - name: "Install os packages"
        run: |
          sudo apt update
          sudo apt install libgl1-mesa-glx xvfb

      - name: "Test virtual framebuffer"
        run: |
          pip install -r .ci/requirements_test_xvfb.txt
          xvfb-run python .ci/display_test.py

      - name: Install ansys-mapdl-core
        run: |
          python -m pip install build
          python -m build
          python -m pip install dist/*.whl
          xvfb-run python -c "from ansys.mapdl import core as pymapdl; print(pymapdl.Report())"

      - name: "Unit testing requirements installation"
        run: |
          python -m pip install .[tests]

      - name: "Waiting for the services to be up"
        run: |
          .ci/waiting_services.sh

      - name: "Unit testing"
        env:
          DISTRIBUTED_MODE: ${{ steps.distributed_mode.outputs.distributed_mode }}
        run: |
          if [[ "${{ matrix.mapdl-version }}" == *"ubuntu"* ]]; then export ON_UBUNTU=true; else export ON_UBUNTU=false; fi
          echo "ON_UBUNTU: $ON_UBUNTU"
          xvfb-run pytest \
            ${{ env.PYTEST_ARGUMENTS }} \
            --ignore_image_cache \
            --cov-report=xml:centos-${{ matrix.mapdl-version }}-remote.xml

      - uses: codecov/codecov-action@v4
        name: "Upload coverage to Codecov"
        with:
          token: ${{ secrets.CODECOV_TOKEN }} # required
          name: centos-${{ matrix.mapdl-version }}-remote.xml
          flags: centos,remote,${{ matrix.mapdl-version }}

      - name: Upload coverage artifacts
        uses: actions/upload-artifact@v4
        with:
          name: centos-${{ matrix.mapdl-version }}-remote.xml
          path: ./centos-${{ matrix.mapdl-version }}-remote.xml

      - name: "Check package"
        run: |
          pip install twine
          twine check dist/*

      - name: "Upload wheel and binaries"
        uses: actions/upload-artifact@v4
        with:
          name: PyMAPDL-packages-${{ matrix.mapdl-version }}
          path: dist/
          retention-days: 7

      - name: "Collect logs on failure"
        if: always()
        env:
          MAPDL_VERSION: ${{ matrix.mapdl-version }}
          MAPDL_INSTANCE: mapdl
          LOG_NAMES: logs-${{ matrix.mapdl-version }}
        run: |
          .ci/collect_mapdl_logs.sh

      - name: "Upload logs to GitHub"
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: logs-${{ matrix.mapdl-version }}.tgz
          path: ./logs-${{ matrix.mapdl-version }}.tgz

      - name: "Display files structure"
        if: always()
        env:
          MAPDL_INSTANCE: mapdl
          LOG_NAMES: logs-${{ matrix.mapdl-version }}
        run: |
          .ci/display_logs.sh

  build-test-ubuntu:
    name: "Local: Build & test on Ubuntu MAPDL ${{ matrix.mapdl-image }} (Extended testing ${{ matrix.extended_testing }})"
    runs-on: ubuntu-latest
    if: github.ref != 'refs/heads/main' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
    needs: [smoke-tests]
    timeout-minutes: 55
    outputs:
      pushed: ${{ steps.attatch-to-pr.outputs.pushed }}
      committed: ${{ steps.attatch-to-pr.outputs.committed }}
      commit_long_sha: ${{ steps.attatch-to-pr.outputs.commit_long_sha }}
    strategy:
      fail-fast: false
      matrix:
        mapdl-image: [
          'v23.2-ubuntu',
          'v24.1-ubuntu',
          'v24.1-ubuntu-student',
          'latest-ubuntu',
          'latest-ubuntu-student',
          ]
        extended_testing:
          - ${{ github.event_name == 'schedule' || ( github.event_name == 'workflow_dispatch' && inputs.run_all_tests ) || ( github.event_name == 'push' && contains(github.ref, 'refs/tags') ) }}
        exclude:
          - extended_testing: false
            mapdl-image: 'v23.2-ubuntu'
          - extended_testing: false
            mapdl-image: 'v24.1-ubuntu'
          - extended_testing: false
            mapdl-image: 'latest-ubuntu'
    container:
      image: ghcr.io/ansys/mapdl:${{ matrix.mapdl-image }}
      options: -u=0:0 --oom-kill-disable --memory=6656MB --memory-swap=16896MB --shm-size=1gb --entrypoint /bin/bash
      credentials:
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}
    env:
      ON_LOCAL: true
      ON_UBUNTU: true
      ON_STUDENT: true
      P_SCHEMA: "/ansys_inc/v241/ansys/ac4/schema"
      PYTEST_TIMEOUT: 120 # seconds. Limit the duration for each unit test

    steps:
      - name: "Install Git and checkout project"
        uses: actions/checkout@v4.1.7
        with:
          repository: ${{ github.event.pull_request.head.repo.full_name }}
          ref: ${{ github.event.pull_request.head.ref }}

      - name: "Setup Python"
        uses: actions/setup-python@v5
        with:
          python-version: ${{ env.MAIN_PYTHON_VERSION }}

      - name: "Checking Python"
        run: |
          python --version
          python -m pip install --upgrade pip
          python -m venv ./.venv
          source ./.venv/bin/activate

      - name: "Install OS packages"
        run: |
          apt update
          apt install -y libgl1-mesa-glx xvfb libgomp1

      - name: "Test virtual framebuffer"
        run: |
          python -m pip install -r .ci/requirements_test_xvfb.txt
          xvfb-run python .ci/display_test.py

      - name: "Install ansys-mapdl-core"
        run: |
          python -m pip install build
          python -m build
          python -m pip install dist/*.whl
          xvfb-run python -c "from ansys.mapdl import core as pymapdl; print(pymapdl.Report())"

      - name: "Unit testing requirements installation"
        run: |
          python -m pip install .[tests]

      - name: "Unit testing"
        env:
          ANSYSLMD_LICENSE_FILE: "1055@${{ secrets.LICENSE_SERVER }}"
        run: |
          unset PYMAPDL_PORT
          unset PYMAPDL_START_INSTANCE
          export AWP_ROOT222=/ansys_inc
          xvfb-run pytest -k "not test_dpf" \
            ${{ env.PYTEST_ARGUMENTS }} \
            --reset_only_failed --add_missing_images \
            --cov-report=xml:${{ matrix.mapdl-image }}-local.xml

      - name: "Adding the directory as safe directory for later step"
        run: |
          git config --global --add safe.directory $GITHUB_WORKSPACE

      - name: "Attaching modified files to PR"
        id: attatch-to-pr
        uses: EndBug/add-and-commit@v9
        with:
          message: Update the image cache
          committer_name: GitHub Actions
          committer_email: actions@github.com
          add: './tests/.image_cache/*.png'

      - name: "PR comment with reactions"
        if: ${{ steps.attatch-to-pr.outputs.pushed == 'true' }}
        uses: thollander/actions-comment-pull-request@v2
        with:
          message: |
            Hello!  :wave:

            Your PR is changing the image cache. So I am attaching the new image cache in a new [commit](https://github.com/ansys/pymapdl/commit/${{ steps.attatch-to-pr.outputs.commit_long_sha }}).

            This commit does not re-run the CICD workflows (since no changes are made in the codebase) therefore you will see the actions showing in their status `Expected — Waiting for status to be reported`. Do not worry. You commit workflow is still running [here](https://github.com/ansys/pymapdl/pull/${{ github.event.pull_request.number }}/checks?sha=${{ github.event.pull_request.head.sha }}) :smile:

            You might want to rerun the test to make sure that everything is passing. You can retrigger the CICD sending an empty commit `git commit -m "Empty comment to trigger CICD" --allow-empty`.

            You will see this message everytime your commit changes the image cache but you are not attaching the updated cache. :nerd_face:

          reactions: rocket

      - uses: codecov/codecov-action@v4
        name: "Upload coverage to Codecov"
        with:
          token: ${{ secrets.CODECOV_TOKEN }} # required
          root_dir: ${{ github.workspace }}
          name: ${{ matrix.mapdl-image }}-local.xml
          flags: ubuntu,local,${{ matrix.mapdl-image }}

      - name: "Upload coverage artifacts"
        uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.mapdl-image }}-local.xml
          path: ./${{ matrix.mapdl-image }}-local.xml

  build-test-ubuntu-minimal:
    name: "Local: Build & test minimal setup on Ubuntu MAPDL ${{ matrix.mapdl-image }} (Extended testing ${{ matrix.extended_testing }})"
    runs-on: ubuntu-latest
    if: github.ref != 'refs/heads/main' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
    needs: [smoke-tests]
    timeout-minutes: 55
    strategy:
      fail-fast: false
      matrix:
        mapdl-image: [
          'v23.2-ubuntu',
          'v24.1-ubuntu',
          'v24.1-ubuntu-student',
          'latest-ubuntu',
          'latest-ubuntu-student',
          ]
        extended_testing:
          - ${{ github.event_name == 'schedule' || ( github.event_name == 'workflow_dispatch' && inputs.run_all_tests ) || ( github.event_name == 'push' && contains(github.ref, 'refs/tags') ) }}
        exclude:
          - extended_testing: false
            mapdl-image: 'v23.2-ubuntu'
          - extended_testing: false
            mapdl-image: 'v24.1-ubuntu'
          - extended_testing: false
            mapdl-image: 'latest-ubuntu'
    container:
      image: ghcr.io/ansys/mapdl:${{ matrix.mapdl-image }}
      options: -u=0:0 --oom-kill-disable --memory=6656MB --memory-swap=16896MB --shm-size=1gb --entrypoint /bin/bash
      credentials:
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}
    env:
      ON_LOCAL: true
      ON_UBUNTU: true
      ON_STUDENT: true

    steps:
      - name: "Install Git and checkout project"
        uses: actions/checkout@v4.1.7
        with:
          repository: ${{ github.event.pull_request.head.repo.full_name }}
          ref: ${{ github.event.pull_request.head.ref }}

      - name: "Installing missing package"
        run: |
          sudo apt-get update
          sudo apt-get install -y libgomp1

      - name: "Setup Python"
        uses: actions/setup-python@v5
        with:
          python-version: ${{ env.MAIN_PYTHON_VERSION }}

      - name: "Checking Python"
        run: |
          python --version
          python -m pip install --upgrade pip

      - name: "Install ansys-mapdl-core"
        run: |
          python -m pip install . --no-deps
          python -m pip install -r minimum_requirements.txt
          python -c "from ansys.mapdl import core as pymapdl; print('Import successfull')"

      - name: "Unit testing requirements installation"
        run: |
          python -m pip install pytest pytest-rerunfailures pytest-cov

      - name: "Unit testing"
        env:
          ANSYSLMD_LICENSE_FILE: "1055@${{ secrets.LICENSE_SERVER }}"
        run: |
          # Because there is no 'ansys-tools-path' we need to input the
          # executable path with the env var: PYMAPDL_MAPDL_EXEC.

          if [[ "${{ matrix.mapdl-image }}" == *"latest-ubuntu"* ]] ; then
            version="242"
          else
            version=$(echo "${{ matrix.mapdl-image }}" | head -c 5 | tail -c 4 | tr -d '.')
          fi;

          echo "$version"

          export PYMAPDL_MAPDL_EXEC=/ansys_inc/v"$version"/ansys/bin/ansys"$version"
          echo "$PYMAPDL_MAPDL_EXEC"

          unset PYMAPDL_START_INSTANCE
          pytest -k "not test_dpf" \
            ${{ env.PYTEST_ARGUMENTS }} \
            --cov-report=xml:${{ matrix.mapdl-image }}-minimal.xml

      - uses: codecov/codecov-action@v4
        name: "Upload coverage to Codecov"
        with:
          token: ${{ secrets.CODECOV_TOKEN }} # required
          root_dir: ${{ github.workspace }}
          name: ${{ matrix.mapdl-image }}-minimal.xml
          flags: ubuntu,local,${{ matrix.mapdl-image }},minimal

      - name: "Upload coverage artifacts"
        uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.mapdl-image }}-minimal.xml
          path: ./${{ matrix.mapdl-image }}-minimal.xml

  test-windows:
    # Skipped
    if: github.repository == ''
    name: "Local: Build & test on Windows"
    runs-on: [self-hosted, Windows, pymapdl]
    timeout-minutes: 30
    env:
      ON_LOCAL: TRUE

    steps:
      - uses: actions/checkout@v4.1.7

      # Skipping because it is installed locally.
      # - name: Setup Python
      #   uses: actions/setup-python@v5
      #   with:
      #     python-version: 3.9

      - name: "Checking Python"
        shell: powershell
        run: |
          python -m pip install --upgrade pip

      - name: "Creating python venv"
        shell: powershell
        run: |
          python -m venv .\.venv
          .\.venv\Scripts\activate

      - name: "Install ansys-mapdl-core"
        shell: powershell
        run: |
          python -m pip install build
          python -m build
          $FILE_=Resolve-Path '.\dist\*.whl'
          python -m pip install $FILE_.Path --upgrade
          python -c "from ansys.mapdl import core as pymapdl; print(pymapdl.Report())"

      - name: "Unit testing requirements installation"
        shell: powershell
        run: |
          python -m pip install .[tests]

      # - name: DPF Server Activation
      #   run: |
      #     docker pull ghcr.io/ansys/dpf-core:22.2dev
      #     docker run -d --name dpfserver -p ${{ env.DPF_PORT }}:50052 ghcr.io/ansys/dpf-core:22.2dev && echo "DPF Server active on port ${{ env.DPF_PORT }}."

      - name: "Unit testing"
        shell: powershell
        run: |
          set PYMAPDL_PORT=
          set PYMAPDL_START_INSTANCE=
          python -m pytest -k "not test_database and not test_dpf" \
            ${{ env.PYTEST_ARGUMENTS }} \
            --ignore_image_cache \
            --cov-report=xml:windows-v22.2.0-local.xml

      - uses: codecov/codecov-action@v4
        name: "Upload coverage to Codecov"
        with:
          token: ${{ secrets.CODECOV_TOKEN }} # required
          name: windows-v22.2.0-local.xml
          flags: windows,local,v22.2.0

      - name: "Upload coverage artifacts"
        uses: actions/upload-artifact@v4
        with:
          name: windows-v22.2.0-local.xml
          path: ./windows_local.xml

  package:
    name: "Package library"
    needs: [build-test, build-test-ubuntu, build-test-ubuntu-minimal, docs-build]
    runs-on: ubuntu-latest
    steps:
      - name: "Build library source and wheel artifacts"
        uses: ansys/actions/build-library@v6
        with:
          library-name: ${{ env.PACKAGE_NAME }}
          python-version: ${{ env.MAIN_PYTHON_VERSION }}

  release:
    name: "Release project"
    if: ${{ github.event_name == 'push' && contains(github.ref, 'refs/tags') }}
    needs: [package, update-changelog]
    runs-on: ubuntu-latest
    # Specifying a GitHub environment is optional, but strongly encouraged
    environment: release
    permissions:
      id-token: write
      contents: write
    steps:
      - name: "Release to the public PyPI repository"
        uses: ansys/actions/release-pypi-public@v6
        with:
          library-name: ${{ env.PACKAGE_NAME }}
          use-trusted-publisher: true

      - name: "Release to GitHub"
        uses: ansys/actions/release-github@v6
        with:
          library-name: ${{ env.PACKAGE_NAME }}
          additional-artifacts: "minimum_requirements.txt"

      - name: "Display structure of downloaded files"
        run: ls -Rla

  upload-docs-release:
    name: "Upload release documentation"
    if: github.event_name == 'push' && contains(github.ref, 'refs/tags')
    runs-on: ubuntu-latest
    needs: [release]
    steps:
      - name: "Deploy the stable documentation"
        # TODO: testing SEO improvements. This branch avoids creating a
        # sitemap.xml pages in opposite to v5.
        uses: ansys/actions/doc-deploy-stable@feat/seo-improvements
        with:
          cname: ${{ env.DOCUMENTATION_CNAME }}
          token: ${{ secrets.GITHUB_TOKEN }}
          render-last: '5'

  doc-index-stable:
    name: "Deploy stable docs index"
    runs-on: ubuntu-latest
    needs: upload-docs-release
    steps:
      - name: "Install Git and clone project"
        uses: actions/checkout@v4.1.7

      - name: "Install the package requirements"
        run: pip install -e .

      - name: "Get the version to PyMeilisearch"
        run: |
          VERSION=$(python -c "from ansys.mapdl.core import __version__; print('.'.join(__version__.split('.')[:2]))")
          VERSION_MEILI=$(python -c "from ansys.mapdl.core import __version__; print('-'.join(__version__.split('.')[:2]))")
          echo "Calculated VERSION: $VERSION"
          echo "Calculated VERSION_MEILI: $VERSION_MEILI"
          echo "VERSION=$VERSION" >> $GITHUB_ENV
          echo "VERSION_MEILI=$VERSION_MEILI" >> $GITHUB_ENV

      - name: "Deploy the latest documentation index"
        uses: ansys/actions/doc-deploy-index@v6
        with:
          cname: ${{ env.DOCUMENTATION_CNAME }}/version/${{ env.VERSION }}
          index-name: pymapdl-v${{ env.VERSION_MEILI }}
          host-url: ${{ vars.MEILISEARCH_HOST_URL }}
          api-key: ${{ env.MEILISEARCH_API_KEY }}

  upload-dev-docs:
    name: "Upload dev documentation"
    if: github.ref == 'refs/heads/main' && !contains(github.ref, 'refs/tags')
    runs-on: ubuntu-latest
    needs: [docs-build]
    steps:
      - name: "Deploy the latest documentation"
        # TODO: testing SEO improvements. This branch reuses the "index.html" from the stable version
        uses: ansys/actions/doc-deploy-dev@feat/seo-improvements
        with:
          cname: ${{ env.DOCUMENTATION_CNAME }}
          token: ${{ secrets.GITHUB_TOKEN }}

  doc-index-dev:
    name: "Deploy dev index docs"
    runs-on: ubuntu-latest
    needs: upload-dev-docs
    steps:
      - name: "Deploy the latest documentation index"
        uses: ansys/actions/doc-deploy-index@v6
        with:
          cname: ${{ env.DOCUMENTATION_CNAME }}/version/dev
          index-name: pymapdl-vdev
          host-url: ${{ vars.MEILISEARCH_HOST_URL }}
          api-key: ${{ env.MEILISEARCH_API_KEY }}

  notify:
    name: "Notify failed build"
    needs: [smoke-tests, docs-build, build-test, build-test-ubuntu, build-test-ubuntu-minimal]
    if: failure() && github.event_name == 'schedule'
    runs-on: ubuntu-latest
    steps:
      - name: "Open issue"
        uses: jayqi/failed-build-issue-action@v1
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          title-template: "Failed scheduled build"
          label-name: "Build failed"

  test_julia:
    name: "Test julia implementation"
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        julia-version: ['1.10.2']  # '1.6.7'
        os: [ubuntu-latest, windows-latest]

    steps:
      - uses: actions/checkout@v4.1.7
      - name: "Set up Julia"
        uses: julia-actions/setup-julia@v2
        with:
          version: ${{ matrix.julia-version }}

      - name: "Getting python interpreter"
        id: get_python
        run: |
          pycallpython=$(julia -e 'using Pkg;Pkg.add("PyCall");using PyCall;println(PyCall.python)')
          echo "pythonpath=$(echo $pycallpython)" >> $GITHUB_OUTPUT

      - name: "Installing PyMAPDL"
        env:
          PYTHON_PATH: ${{ steps.get_python.outputs.pythonpath }}
        run: |
          "$PYTHON_PATH" -m pip install -e .

      - name: "Starting Julia"
        shell: julia {0}
        run: |
          using Pkg; Pkg.add("PyCall");using PyCall;pymapdl = pyimport("ansys.mapdl.core");print(pymapdl.__version__)


Pull Request Labeler

name: "Pull Request Labeler"
on:
  pull_request:
    # opened, reopened, and synchronize are default for pull_request
    # edited - when PR title or body is changed
    # labeled - when labels are added to PR
    types: [opened, reopened, synchronize, edited, labeled]
  push:
    branches: [ main ]
    paths:
      - '../labels.yml'

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:

  label-syncer:
    # Sync the labels name, colours with the file ``labels.yml``
    name: Syncer
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4.1.7
      - uses: micnncim/action-label-syncer@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  labeler:
    name: Set labels
    needs: [label-syncer]
    permissions:
      contents: read
      pull-requests: write
    runs-on: ubuntu-latest
    steps:

    # Label based on modified files
    - name: Label based on changed files
      uses: actions/labeler@v5
      with:
        repo-token: "${{ secrets.GITHUB_TOKEN }}"
        sync-labels: true

    # Label based on branch name
    - uses: actions-ecosystem/action-add-labels@v1
      if: |
        startsWith(github.event.pull_request.head.ref, 'doc') ||
        startsWith(github.event.pull_request.head.ref, 'docs')
      with:
        github_token: ${{ secrets.GITHUB_TOKEN}}
        labels: documentation

    - uses: actions-ecosystem/action-add-labels@v1
      if: |
        startsWith(github.event.pull_request.head.ref, 'docker') ||
        startsWith(github.event.pull_request.head.ref, 'no-ci') ||
        startsWith(github.event.pull_request.head.ref, 'ci')
      with:
        github_token: ${{ secrets.GITHUB_TOKEN}}
        labels: CI/CD

    - uses: actions-ecosystem/action-add-labels@v1
      if: startsWith(github.event.pull_request.head.ref, 'maint')
      with:
        github_token: ${{ secrets.GITHUB_TOKEN}}
        labels: maintenance

    - uses: actions-ecosystem/action-add-labels@v1
      if: startsWith(github.event.pull_request.head.ref, 'feat')
      with:
        github_token: ${{ secrets.GITHUB_TOKEN}}
        labels: |
          enhancement
          new feature

    - uses: actions-ecosystem/action-add-labels@v1
      if: |
        startsWith(github.event.pull_request.head.ref, 'fix') ||
        startsWith(github.event.pull_request.head.ref, 'patch')
      with:
        github_token: ${{ secrets.GITHUB_TOKEN}}
        labels: bug

  commenter:
    runs-on: ubuntu-latest
    needs: [labeler]
    permissions:
      pull-requests: write
    steps:
    - name: Suggest to add labels
      uses: peter-evans/create-or-update-comment@v4
      # Execute only when no labels have been applied to the pull request
      if: toJSON(github.event.pull_request.labels.*.name) == '{}' # Empty labels
      with:
        token: ${{ secrets.GITHUB_TOKEN }}
        issue-number: ${{ github.event.pull_request.number }}
        body: |
          Please add one of the following labels to add this contribution to the Release Notes :point_down:
          - [bug](https://github.com/ansys/pymapdl/pulls?q=label%3Abug+)
          - [new feature](https://github.com/ansys/pymapdl/pulls?q=label%3A"new%20feature"+)
          - [enhancement](https://github.com/ansys/pymapdl/pulls?q=label%3Aenhancement+)
          - [documentation](https://github.com/ansys/pymapdl/pulls?q=label%3Adocumentation+)
          - [examples](https://github.com/ansys/pymapdl/pulls?q=label%3Aexamples+)
          - [maintenance](https://github.com/ansys/pymapdl/pulls?q=label%3Amaintenance+)
          - [CI/CD](https://github.com/ansys/pymapdl/pulls?q=label%3Aci%2Fcd+)

  changelog-fragment:
    name: "Create changelog fragment"
    needs: [labeler]
    permissions:
      contents: write
      pull-requests: write
    runs-on: ubuntu-latest
    steps:
    - uses: ansys/actions/doc-changelog@v6
      with:
        token: ${{ secrets.PYANSYS_CI_BOT_TOKEN }}


Approve PRs

name: Approve PRs
on:
  workflow_dispatch:
    inputs:
      user:
        description: 'Username'
        required: true
        type: string
      html_url:
        description: 'URL to the comment'
        required: true
        type: string
      pr:
        description: 'Pull request to approve'
        required: true
        type: number
      commentid:
        description: 'Comment ID number'
        type: number
        required: true

  issue_comment:
    types: [created, edited]

jobs:
  autoapprove:
    # This job only runs for pull request comments
    name: PR comment
    if: (github.event.issue.pull_request &&
        contains(github.event.comment.body, '@pyansys-ci-bot LGTM') && (
          github.event.comment.user.login == 'germa89'
        )) || ( github.event_name == 'workflow_dispatch' )
    permissions:
      pull-requests: write
    runs-on: ubuntu-latest
    steps:

      - name: "Settings"
        id: settings
        run: |
          export event_name=${{ github.event_name }}

          if [[ $event_name == "workflow_dispatch" ]] ; then
            echo "On workflow dispatch"
            echo "user=${{ inputs.user }}" >> $GITHUB_OUTPUT
            echo "html_url=${{ inputs.html_url }}" >> $GITHUB_OUTPUT
            echo "pull_request=${{ inputs.pr }}" >> $GITHUB_OUTPUT
            echo "commentid=${{ inputs.commentid }}" >> $GITHUB_OUTPUT

          else
            echo "On $event_name"
            echo "user=${{ github.event.comment.user.login }}" >> $GITHUB_OUTPUT
            echo "html_url=${{ github.event.comment.html_url }}" >> $GITHUB_OUTPUT
            echo "pull_request=${{ github.event.issue.number }}" >> $GITHUB_OUTPUT
            echo "commentid=${{ github.event.comment.id }}" >> $GITHUB_OUTPUT

          fi;

      - name: React to comment
      #https://github.com/ansys/pymapdl/pull/2654#issuecomment-1889009514
        uses: dkershner6/reaction-action@v2 # You can also use a specific version, e.g. v2.0.0
        with:
          token: ${{ secrets.PYANSYS_CI_BOT_TOKEN }}
          commentId: ${{ steps.settings.outputs.commentid }} # Optional if the trigger is a comment. Use another action to find this otherwise.
          reaction: "+1" # Optional

      - name: "Grab url for GIF"
        id: image_grabber
        run: |
          sudo apt-get install jq
          export IMG_MSG=$(curl -s 'https://us-central1-lgtm-reloaded.cloudfunctions.net/lgtm' | jq -r '.markdown' | grep -v 'Powered By GIPHY')
          echo "IMG_MSG=$IMG_MSG" >> $GITHUB_OUTPUT

      - uses: hmarr/auto-approve-action@v4
        with:
          review-message: |
            :white_check_mark: Approving this PR because [${{ steps.settings.outputs.user }}](https://github.com/${{ steps.settings.outputs.user }}) said so in [here](${{ steps.settings.outputs.html_url }}) :grimacing:

            ${{ steps.image_grabber.outputs.IMG_MSG }}

          pull-request-number: ${{ steps.settings.outputs.pull_request }}
          github-token: ${{ secrets.PYANSYS_CI_BOT_TOKEN }}


Cleanup caches 🧹

name: Cleanup caches 🧹
on:
  workflow_dispatch:
  pull_request:
    types:
      - closed

jobs:
  cleanup:
    runs-on: ubuntu-latest
    steps:
      - name: Check out code
        uses: actions/checkout@v4.1.7

      - name: Cleanup PR caches
        if: github.event_name != 'workflow_dispatch'
        run: |
          gh extension install actions/gh-actions-cache

          REPO=${{ github.repository }}
          BRANCH="refs/pull/${{ github.event.pull_request.number }}/merge"

          echo "Fetching list of cache key"
          cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 )

          ## Setting this to not fail the workflow while deleting cache keys.
          set +e
          echo "Deleting caches..."

          while [ ! -z "$cacheKeysForPR" ];
          do
            for cacheKey in $cacheKeysForPR
            do
                gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm && echo "Deleting cache with key: $cacheKey"
            done

            cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 )
          done
          echo "Done"
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Cleanup by workflow dispatch
        if: github.event_name == 'workflow_dispatch'
        run: |
            gh extension install actions/gh-actions-cache

            REPO=${{ github.repository }}
            echo $REPO

            echo "Fetching list of cache key"
            cacheKeysForPR=$(gh actions-cache list -R $REPO | cut -f 1 )

            ## Setting this to not fail the workflow while deleting cache keys.
            set +e
            echo "Deleting caches..."
            while [ ! -z "$cacheKeysForPR" ];
            do
              for cacheKey in $cacheKeysForPR
              do
                  gh actions-cache delete $cacheKey -R $REPO --confirm && echo "Deleting cache with key: $cacheKey"
              done

              cacheKeysForPR=$(gh actions-cache list -R $REPO | cut -f 1 )
            done
            echo "Done"
        env:
            GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}


LinkCheck - All files 🔗

name: LinkCheck - All files 🔗

on:
  schedule:
    - cron: "0 5 * * 0" # Runs at 05:00 on Sunday.
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

env:
  ON_CI: True
  MAPDL_PACKAGE: ghcr.io/ansys/mapdl
  MAPDL_IMAGE_VERSION_DOCS_BUILD: v24.1.0
  MAIN_PYTHON_VERSION: '3.10'
  PYANSYS_OFF_SCREEN: True
  DPF_START_SERVER: False
  DPF_PORT: 21002

jobs:
  linkchecker:
    name: Check Links
    runs-on: ubuntu-latest
    timeout-minutes: 60
    env:
      PYMAPDL_PORT: 21000  # default won't work on GitHub runners
      PYMAPDL_DB_PORT: 21001  # default won't work on GitHub runners
      PYMAPDL_START_INSTANCE: FALSE
      ON_DOCUMENTATION: TRUE
      SHELLOPTS: "errexit:pipefail"
    steps:
      - name: "Install Git and checkout project"
        uses: actions/checkout@v4.1.7

      - name: "Login in Github container registry"
        uses: docker/login-action@v3.2.0
        with:
          registry: ghcr.io
          username: ${{ secrets.GH_USERNAME }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: "DPF server activation"
        run: |
          $(docker pull ghcr.io/ansys/dpf-core:22.2dev && docker run -d --name dpfserver -p ${{ env.DPF_PORT }}:50052 ghcr.io/ansys/dpf-core:22.2dev && echo "DPF Server active on port ${{ env.DPF_PORT }}.") &

      - name: "Pull, launch, and validate MAPDL service"
        id: start_mapdl
        env:
          LICENSE_SERVER: ${{ secrets.LICENSE_SERVER }}
          MAPDL_VERSION: ${{ env.MAPDL_IMAGE_VERSION_DOCS_BUILD }}
          DISTRIBUTED_MODE: "dmp"
        run: |
          export INSTANCE_NAME=MAPDL
          .ci/start_mapdl.sh &> mapdl_launch.log & export DOCKER_PID=$!
          echo "Launching MAPDL service at PID: $DOCKER_PID"
          echo "DOCKER_PID=$(echo $DOCKER_PID)" >> $GITHUB_OUTPUT

      - name: "Setup Python"
        uses: actions/setup-python@v5
        with:
          python-version: ${{ env.MAIN_PYTHON_VERSION }}

      - name: "Install OS packages"
        run: |
          sudo apt update
          sudo apt install zip pandoc libgl1-mesa-glx xvfb texlive-latex-extra latexmk graphviz

      - name: "Test virtual framebuffer"
        run: |
          pip install -r .ci/requirements_test_xvfb.txt
          xvfb-run python .ci/display_test.py

      - name: "Install ansys-mapdl-core"
        run: |
          pip install .
          xvfb-run python -c "from ansys.mapdl import core as pymapdl; print(pymapdl.Report())"

      - name: "Retrieve PyMAPDL version"
        id: version
        run: |
          echo "PYMAPDL_VERSION=$(python -c 'from ansys.mapdl.core import __version__; print(__version__)')" >> $GITHUB_OUTPUT
          echo "PyMAPDL version is: $(python -c "from ansys.mapdl.core import __version__; print(__version__)")"

      - name: "Install docs build requirements"
        run: |
          pip install .[doc]

      - name: "Waiting for the services to be up"
        run: |
          .ci/waiting_services.sh

      - name: LinkCheck
        run: |
          xvfb-run make -C doc linkcheck  SPHINXOPTS="-j auto -W --keep-going"

      - name: "Display linkchecker results"
        if: always()
        run: |
          echo "::group:: Display linkcher output" && cat doc/*/output.txt && echo "::endgroup::" || echo "Failed to display the output file."

      - name: "Upload HTML Documentation"
        uses: actions/upload-artifact@v4
        with:
          name: documentation-html
          path: doc/_build/html
          retention-days: 7

      - name: "Upload artifacts"
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: output
          path: doc/**/output.txt
          retention-days: 30


Docker images - Cleanup

name: Docker images - Cleanup
on:
  workflow_dispatch:
  schedule: # UTC at 0200
    - cron: "0 2 * * *"

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: write
  packages: write

jobs:
  cleanup:
    name: Cleaning unnecessary packages
    runs-on: ubuntu-latest
    env:
      PACKAGE_DELETION_TOKEN: ${{ secrets.GITHUB_TOKEN }}

    steps:

    - name: "Perform versions cleanup - except certain tags"
      uses: ansys/actions/hk-package-clean-untagged@v6
      with:
        package-name: 'mapdl'
        token: ${{ secrets.GITHUB_TOKEN }}
        allow-last-days: 5


CI

name: CI

on:
  merge_group:
  pull_request:
  push:
    # We need to explicitly include tags because otherwise when adding
    # `branches-ignore` it will only trigger on branches.
    tags:
      - '*'
    branches-ignore:
      # Ignore pushes to merge queues.
      # We only want to test the merge commit (`merge_group` event), the hashes
      # in the push were already tested by the PR checks
      - 'gh-readonly-queue/**'
      - 'dependabot/**'
  workflow_dispatch:

env:
  # Please make sure this version is included in the `matrix`, as the
  # `matrix` section can't use `env`, so it must be entered manually
  DEFAULT_PYTHON_VERSION: '3.11'
  # It would be nice to be able to also define a DEFAULT_UBUNTU_VERSION
  # but sadly `env` can't be used either in `runs-on`.

jobs:
  nox:
    name: Test with nox
    strategy:
      fail-fast: false
      matrix:
        os:
          - ubuntu-20.04
        python:
          - "3.11"
          - "3.12"
        nox-session:
          # To speed things up a bit we use the special ci_checks_max session
          # that uses the same venv to run multiple linting sessions
          - "ci_checks_max"
          - "pytest_min"
    runs-on: ${{ matrix.os }}

    steps:
      - name: Print environment (debug)
        run: env

      - name: Fetch sources
        uses: actions/checkout@v4
        with:
          submodules: true

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python }}
          cache: 'pip'

      - name: Install required Python packages
        run: |
          python -m pip install --upgrade pip
          python -m pip install -e .[dev-noxfile]
          pip freeze

      - name: Create nox venv
        env:
          NOX_SESSION: ${{ matrix.nox-session }}
        run: nox --install-only -e "$NOX_SESSION"

      - name: Print pip freeze for nox venv (debug)
        env:
          NOX_SESSION: ${{ matrix.nox-session }}
        run: |
          . ".nox/$NOX_SESSION/bin/activate"
          pip freeze
          deactivate

      - name: Run nox
        env:
          NOX_SESSION: ${{ matrix.nox-session }}
        run: nox -R -e "$NOX_SESSION"
        timeout-minutes: 10

  # This job runs if all the `nox` matrix jobs ran and succeeded.
  # It is only used to have a single job that we can require in branch
  # protection rules, so we don't have to update the protection rules each time
  # we add or remove a job from the matrix.
  nox-all:
    # The job name should match the name of the `nox` job.
    name: Test with nox
    needs: ["nox"]
    # We skip this job only if nox was also skipped
    if: always() && needs.nox.result != 'skipped'
    runs-on: ubuntu-20.04
    env:
      DEPS_RESULT: ${{ needs.nox.result }}
    steps:
      - name: Check matrix job result
        run: test "$DEPS_RESULT" = "success"

  nox-cross-arch:
    name: Cross-arch tests with nox
    if: github.event_name != 'pull_request'
    strategy:
      fail-fast: false
      # Before adding new items to this matrix, make sure that a dockerfile
      # exists for the combination of items in the matrix.
      # Refer to .github/containers/nox-cross-arch/README.md to learn how to
      # add and name new dockerfiles.
      matrix:
        arch:
          - arm64
        os:
          - ubuntu-20.04
        python:
          - "3.11"
          - "3.12"
        nox-session:
          - "pytest_min"
          - "pytest_max"
    runs-on: ${{ matrix.os }}

    steps:
      - name: Fetch sources
        uses: actions/checkout@v4

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3
        with:
          platforms: linux/${{ matrix.arch }}

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      # This is a workaround to prevent the cache from growing indefinitely.
      # https://docs.docker.com/build/ci/github-actions/cache/#local-cache
      # https://github.com/docker/build-push-action/issues/252
      # https://github.com/moby/buildkit/issues/1896
      - name: Cache container layers
        uses: actions/cache@v4
        with:
          path: /tmp/.buildx-cache
          key: ${{ runner.os }}-buildx-nox-${{ matrix.arch }}-${{ matrix.os }}-${{ matrix.python }}

      - name: Build image
        uses: docker/build-push-action@v5
        with:
          context: .github/containers/nox-cross-arch
          file: .github/containers/nox-cross-arch/${{ matrix.arch }}-${{ matrix.os }}-python.Dockerfile
          platforms: linux/${{ matrix.arch }}
          build-args: |
            PYTHON_VERSION=${{ matrix.python }}
          tags: localhost/nox-cross-arch:latest
          push: false
          load: true
          cache-from: type=local,src=/tmp/.buildx-cache
          cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max

      # Refer to the workaround mentioned above
      - name: Move cache
        run: |
          rm -rf /tmp/.buildx-cache
          mv /tmp/.buildx-cache-new /tmp/.buildx-cache

      # Cache pip downloads
      - name: Cache pip downloads
        uses: actions/cache@v4
        with:
          path: /tmp/pip-cache
          key: nox-${{ matrix.nox-session }}-${{ matrix.arch }}-${{ matrix.os }}-${{ matrix.python }}-${{ hashFiles('pyproject.toml') }}

      # This ensures that the docker container has access to the pip cache.
      # Changing the user in the docker-run step causes it to fail due to
      # incorrect permissions. Setting the ownership of the pip cache to root
      # before running is a workaround to this issue.
      - name: Set pip cache owners to root for docker
        run: if [[ -e /tmp/pip-cache ]]; then sudo chown -R root:root /tmp/pip-cache; fi

      - name: Run nox
        run: |
          docker run \
            --rm \
            -v $(pwd):/${{ github.workspace }} \
            -v /tmp/pip-cache:/root/.cache/pip \
            -w ${{ github.workspace }} \
            --net=host \
            --platform linux/${{ matrix.arch }} \
            localhost/nox-cross-arch:latest \
            bash -c "pip install -e .[dev-noxfile]; nox --install-only -e ${{ matrix.nox-session }}; pip freeze; nox -e ${{ matrix.nox-session }}"
        timeout-minutes: 30

      # This ensures that the runner has access to the pip cache.
      - name: Reset pip cache ownership
        if: always()
        run: sudo chown -R $USER:$USER /tmp/pip-cache

  # This job runs if all the `nox-cross-arch` matrix jobs ran and succeeded.
  # As the `nox-all` job, its main purpose is to provide a single point of
  # reference in branch protection rules, similar to how `nox-all` operates.
  # However, there's a crucial difference: the `nox-cross-arch` job is omitted
  # in PRs. Without the `nox-cross-arch-all` job, the inner matrix wouldn't be
  # expanded in such scenarios. This would lead to the CI indefinitely waiting
  # for these jobs to complete due to the branch protection rules, essentially
  # causing it to hang. This behavior is tied to a recognized GitHub matrices
  # issue when certain jobs are skipped. For a deeper understanding, refer to:
  # https://github.com/orgs/community/discussions/9141
  nox-cross-arch-all:
    # The job name should match the name of the `nox-cross-arch` job.
    name: Cross-arch tests with nox
    needs: ["nox-cross-arch"]
    # We skip this job only if nox-cross-arch was also skipped
    if: always() && needs.nox-cross-arch.result != 'skipped'
    runs-on: ubuntu-20.04
    env:
      DEPS_RESULT: ${{ needs.nox-cross-arch.result }}
    steps:
      - name: Check matrix job result
        run: test "$DEPS_RESULT" = "success"

  build:
    name: Build distribution packages
    runs-on: ubuntu-20.04
    steps:
      - name: Fetch sources
        uses: actions/checkout@v4
        with:
          submodules: true

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
          cache: 'pip'

      - name: Install required Python packages
        run: |
          python -m pip install -U pip
          python -m pip install -U build
          pip freeze

      - name: Build the source and binary distribution
        run: python -m build

      - name: Upload distribution files
        uses: actions/upload-artifact@v4
        with:
          name: dist-packages
          path: dist/
          if-no-files-found: error

  test-installation:
    name: Test package installation in different architectures
    needs: ["build"]
    strategy:
      fail-fast: false
      matrix:
        os:
          - ubuntu-20.04
        python:
          - "3.11"
          - "3.12"
    runs-on: ${{ matrix.os }}
    steps:
      - name: Fetch sources
        uses: actions/checkout@v4
      - name: Download package
        uses: actions/download-artifact@v4
        with:
          name: dist-packages
          path: dist
      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3
      - name: Set up docker-buildx
        uses: docker/setup-buildx-action@v3
      - name: Test Installation
        uses: docker/build-push-action@v5
        with:
          context: .
          file: .github/containers/test-installation/Dockerfile
          platforms: linux/amd64,linux/arm64
          build-args: |
            PYTHON_VERSION=${{ matrix.python }}
          tags: localhost/test-installation
          push: false

  # This job runs if all the `test-installation` matrix jobs ran and succeeded.
  # It is only used to have a single job that we can require in branch
  # protection rules, so we don't have to update the protection rules each time
  # we add or remove a job from the matrix.
  test-installation-all:
    # The job name should match the name of the `test-installation` job.
    name: Test package installation in different architectures
    needs: ["test-installation"]
    # We skip this job only if test-installation was also skipped
    if: always() && needs.test-installation.result != 'skipped'
    runs-on: ubuntu-20.04
    env:
      DEPS_RESULT: ${{ needs.test-installation.result }}
    steps:
      - name: Check matrix job result
        run: test "$DEPS_RESULT" = "success"

  test-docs:
    name: Test documentation website generation
    if: github.event_name != 'push'
    runs-on: ubuntu-20.04
    steps:
      - name: Fetch sources
        uses: actions/checkout@v4
        with:
          submodules: true

      - name: Setup Git user and e-mail
        uses: frequenz-floss/setup-git-user@v2

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
          cache: 'pip'

      - name: Install build dependencies
        run: |
          python -m pip install -U pip
          python -m pip install .[dev-mkdocs]
          pip freeze

      - name: Generate the documentation
        env:
          MIKE_VERSION: gh-${{ github.job }}
        run: |
          mike deploy $MIKE_VERSION
          mike set-default $MIKE_VERSION

      - name: Upload site
        uses: actions/upload-artifact@v4
        with:
          name: docs-site
          path: site/
          if-no-files-found: error

  publish-docs:
    name: Publish documentation website to GitHub pages
    needs: ["nox-all", "nox-cross-arch-all", "test-installation"]
    if: github.event_name == 'push'
    runs-on: ubuntu-20.04
    permissions:
      contents: write
    steps:
      - name: Fetch sources
        uses: actions/checkout@v4
        with:
          submodules: true

      - name: Setup Git user and e-mail
        uses: frequenz-floss/setup-git-user@v2

      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ env.DEFAULT_PYTHON_VERSION }}
          cache: 'pip'

      - name: Install build dependencies
        run: |
          python -m pip install -U pip
          python -m pip install .[dev-mkdocs]
          pip freeze

      - name: Calculate and check version
        id: mike-version
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITHUB_REPO: ${{ github.repository }}
          GIT_REF: ${{ github.ref }}
          GIT_SHA: ${{ github.sha }}
        run: |
          python -m frequenz.repo.config.cli.version.mike.info

      - name: Fetch the gh-pages branch
        if: steps.mike-version.outputs.version
        run: git fetch origin gh-pages --depth=1

      - name: Build site
        if: steps.mike-version.outputs.version
        env:
          VERSION: ${{ steps.mike-version.outputs.version }}
          TITLE: ${{ steps.mike-version.outputs.title }}
          ALIASES: ${{ steps.mike-version.outputs.aliases }}
          # This is not ideal, we need to define all these variables here
          # because we need to calculate all the repository version information
          # to be able to show the correct versions in the documentation when
          # building it.
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          GITHUB_REPO: ${{ github.repository }}
          GIT_REF: ${{ github.ref }}
          GIT_SHA: ${{ github.sha }}
        run: |
          mike deploy --update-aliases --title "$TITLE" "$VERSION" $ALIASES

      - name: Sort site versions
        if: steps.mike-version.outputs.version
        run: |
          git checkout gh-pages
          python -m frequenz.repo.config.cli.version.mike.sort versions.json
          git commit -a -m "Sort versions.json"

      - name: Publish site
        if: steps.mike-version.outputs.version
        run: |
          git push origin gh-pages

  create-github-release:
    name: Create GitHub release
    needs: ["publish-docs"]
    # Create a release only on tags creation
    if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
    permissions:
      # We need write permissions on contents to create GitHub releases and on
      # discussions to create the release announcement in the discussion forums
      contents: write
      discussions: write
    runs-on: ubuntu-20.04
    steps:
      - name: Download distribution files
        uses: actions/download-artifact@v4
        with:
          name: dist-packages
          path: dist

      - name: Download RELEASE_NOTES.md
        run: |
          set -ux
          gh api \
              -X GET \
              -f ref=$REF \
              -H "Accept: application/vnd.github.raw" \
              "/repos/$REPOSITORY/contents/RELEASE_NOTES.md" \
            > RELEASE_NOTES.md
        env:
          REF: ${{ github.ref }}
          REPOSITORY: ${{ github.repository }}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

      - name: Create GitHub release
        run: |
          set -ux
          extra_opts=
          if echo "$REF_NAME" | grep -- -; then extra_opts=" --prerelease"; fi
          gh release create \
            -R "$REPOSITORY" \
            --notes-file RELEASE_NOTES.md \
            --generate-notes \
            $extra_opts \
            $REF_NAME \
            dist/*
        env:
          REF_NAME: ${{ github.ref_name }}
          REPOSITORY: ${{ github.repository }}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

  publish-to-pypi:
    name: Publish packages to PyPI
    needs: ["create-github-release"]
    runs-on: ubuntu-20.04
    permissions:
      # For trusted publishing. See:
      # https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/
      id-token: write
    steps:
      - name: Download distribution files
        uses: actions/download-artifact@v4
        with:
          name: dist-packages
          path: dist

      - name: Publish the Python distribution to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1


DCO

# Based on https://github.com/hyperledger/besu/pull/5207/files
name: DCO
on:
  merge_group:

jobs:
  DCO:
    runs-on: ubuntu-latest
    if: ${{ github.actor != 'dependabot[bot]' }}
    steps:
      - run: echo "This DCO job runs on merge_queue event and doesn't check PR contents"


Pull Request Labeler

name: Pull Request Labeler

on: [pull_request_target]

jobs:
  Label:
    permissions:
      contents: read
      pull-requests: write
    runs-on: ubuntu-latest
    steps:
      - name: Labeler
        # XXX: !!! SECURITY WARNING !!!
        # pull_request_target has write access to the repo, and can read secrets. We
        # need to audit any external actions executed in this workflow and make sure no
        # checked out code is run (not even installing dependencies, as installing
        # dependencies usually can execute pre/post-install scripts). We should also
        # only use hashes to pick the action to execute (instead of tags or branches).
        # For more details read:
        # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
        uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9  # 5.0.0
        with:
          repo-token: "${{ secrets.GITHUB_TOKEN }}"
          dot: true


Release Notes Check

name: Release Notes Check

on:
  merge_group:
  pull_request:
    types:
      # On by default if you specify no types.
      - "opened"
      - "reopened"
      - "synchronize"
      # For `skip-label` only.
      - "labeled"
      - "unlabeled"

jobs:
  check-release-notes:
    name: Check release notes are updated
    runs-on: ubuntu-latest
    steps:
      - name: Check for a release notes update
        if: github.event_name == 'pull_request'
        uses: brettcannon/check-for-changed-files@4170644959a21843b31f1181f2a1761d65ef4791 # v1.2.0
        with:
          file-pattern: "RELEASE_NOTES.md"
          prereq-pattern: "src/**"
          skip-label: "cmd:skip-release-notes"
          failure-message: "Missing a release notes update. Please add one or apply the ${skip-label} label to the pull request"


name: us1 - shell probes (nocturnal / raindrop / vision scripts) deploy

on:
  workflow_dispatch:

jobs:
  build_ubuntu:

    runs-on: ubuntu-latest
    environment: us1

    name: us1 - shell probes (nocturnal / raindrop / vision scripts) deploy

    steps:
      - name: Check out code.
        uses: actions/checkout@v4

      - name: Env Conversion
        shell: bash
        run: |
          for i in shell/probe/nocturnal.sh shell/probe/raindrop.ps1 shell/probe/vision.sh; do
            echo "Endpoint substitution for ${i}"
            sed -i 's!api.preludesecurity.com!api.us1.preludesecurity.com!g' ${i}
          done

      - id: shell_probes_composite
        uses: ./.github/actions/shell_probes
        with:
          s3_bucket: ${{ secrets.BUCKET_NAME }}
          aws_region: ${{ secrets.BUCKET_REGION }}
          aws_access_key_id: ${{ secrets.US1_S3_DEPLOYMENT_ACCESS_KEY_ID }}
          aws_secret_access_key: ${{ secrets.US1_S3_DEPLOYMENT_SECRET_ACCESS_KEY  }}
          slack_notification: ${{ secrets.US1_SLACK_URL }}


name: us2 - shell probes (nocturnal / raindrop / vision scripts) deploy

on:
  workflow_dispatch:

jobs:
  build_ubuntu:

    runs-on: ubuntu-latest
    environment: us2

    name: us2 - shell probes (nocturnal / raindrop / vision scripts) deploy

    steps:
      - name: Check out code.
        uses: actions/checkout@v4

      - name: Env Conversion
        shell: bash
        run: |
          for i in shell/probe/nocturnal.sh shell/probe/raindrop.ps1 shell/probe/vision.sh; do
            echo "Endpoint substitution for ${i}"
            sed -i 's/prelude-account-us1-us-east-2.s3.amazonaws.com/${{ secrets.BUCKET_NAME }}.s3.amazonaws.com/g' ${i}
            sed -i 's!api.preludesecurity.com!api.us2.preludesecurity.com!g' ${i}
          done

      - id: shell_probes_composite
        uses: ./.github/actions/shell_probes
        with:
          s3_bucket: ${{ secrets.BUCKET_NAME }}
          aws_region: ${{ secrets.BUCKET_REGION }}
          aws_access_key_id: ${{ secrets.US2_S3_DEPLOYMENT_ACCESS_KEY_ID }}
          aws_secret_access_key: ${{ secrets.US2_S3_DEPLOYMENT_SECRET_ACCESS_KEY  }}
          slack_notification: ${{ secrets.US2_SLACK_URL }}


name: eu1 - shell probes (nocturnal / raindrop / vision scripts) deploy

on:
  workflow_dispatch:

jobs:
  build_ubuntu:

    runs-on: ubuntu-latest
    environment: eu1

    name: eu1 - shell probes (nocturnal / raindrop / vision scripts) deploy

    steps:
      - name: Check out code.
        uses: actions/checkout@v4

      - name: Env Conversion
        shell: bash
        run: |
          for i in shell/probe/nocturnal.sh shell/probe/raindrop.ps1 shell/probe/vision.sh; do
            echo "Endpoint substitution for ${i}"
            sed -i 's!api.preludesecurity.com!api.eu1.preludesecurity.com!g' ${i}
            sed -i 's/prelude-account-us1-us-east-2.s3.amazonaws.com/${{ secrets.BUCKET_NAME }}.s3.amazonaws.com/g' ${i}
          done

      - id: shell_probes_composite
        uses: ./.github/actions/shell_probes
        with:
          s3_bucket: ${{ secrets.BUCKET_NAME }}
          aws_region: ${{ secrets.BUCKET_REGION }}
          aws_access_key_id: ${{ secrets.EU1_S3_DEPLOYMENT_ACCESS_KEY_ID }}
          aws_secret_access_key: ${{ secrets.EU1_S3_DEPLOYMENT_SECRET_ACCESS_KEY  }}
          slack_notification: ${{ secrets.EU1_SLACK_URL }}


name: scoot-sbx - shell probes (nocturnal / raindrop / vision scripts) deploy

on:
  workflow_dispatch:

jobs:
  build_ubuntu:

    runs-on: ubuntu-latest
    environment: scoot-sbx

    name: scoot-sbx - shell probes (nocturnal / raindrop / vision scripts) deploy

    steps:
      - name: Check out code.
        uses: actions/checkout@v4

      - name: Env Conversion
        shell: bash
        run: |
          for i in shell/probe/nocturnal.sh shell/probe/raindrop.ps1 shell/probe/vision.sh; do
            echo "Endpoint substitution for ${i}"
            sed -i 's/prelude-account-us1-us-east-2.s3.amazonaws.com/${{ secrets.BUCKET_NAME }}.s3.amazonaws.com/g' ${i}
            sed -i 's!api.preludesecurity.com!api.scoot-sbx.preludesecurity.com!g' ${i}
          done

      - id: shell_probes_composite
        uses: ./.github/actions/shell_probes
        with:
          s3_bucket: ${{ secrets.BUCKET_NAME }}
          aws_region: ${{ secrets.BUCKET_REGION }}
          aws_access_key_id: ${{ secrets.SCOOT_SBX_S3_DEPLOYMENT_ACCESS_KEY_ID }}
          aws_secret_access_key: ${{ secrets.SCOOT_SBX_S3_DEPLOYMENT_SECRET_ACCESS_KEY }}
          slack_notification: ${{ secrets.SCOOT_SBX_SLACK_URL }}


circleCI

name: circleCI

on:
  pull_request:
    types: [opened,reopened]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - name: Tickle CircleCI
      env:
        CIRCLE_BRANCH: ${{ github.head_ref }}
        CIRCLE_TOKEN: ${{ secrets.CIRCLE_TOKEN }}
      run: |
        curl -X POST \
        -H "Circle-Token: ${CIRCLE_TOKEN}" \
        -H 'Content-Type: application/json' \
        -H 'Accept: application/json' \
        -d "{\"branch\":\"${CIRCLE_BRANCH}\"}" \
        https://circleci.com/api/v2/project/edenai-apis/pipeline


CICD

# .github/workflows/app.yaml
name: CICD
on:
  workflow_dispatch:
  pull_request:
  push:
    branches:
      - master

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
  contents: write
  pages: write
  id-token: write

jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 20

    steps:
      - name: Check out repository code
        uses: actions/checkout@v3

      - name: Setup Python
        uses: actions/setup-python@v4
        with:
          python-version: "3.11"

      - name: Install dependencies
        run: |
          sudo apt-get update
          sudo apt-get install ffmpeg -y
          pip install -r requirements.txt
      - name: Set Env variables
        run: "echo TEST_SCOPE=CICD-OPENSOURCE >> $GITHUB_ENV"
      - name: Run Test suite
        run: |
          pytest -vvv -s -n auto --maxprocesses=8 --dist loadgroup
      - name: generate list of providers and features markdown
        run: "python -m edenai_apis.scripts.documentation"
      - name: generate doc
        run: |
          cd .docsrc
          rm -rf _build/**
          rm -rf _autosummary/*
          sphinx-apidoc -o _build/ ../edenai_apis/
          make html
          cp -r _build/html/* ../docs/
      - name: commit documentation changes
        uses: EndBug/add-and-commit@v9
        with:
          add: AVAILABLES_FEATURES_AND_PROVIDERS.md
          committer_name: Github Action
          committer_email: mail@example.com
          message: '[doc] update available features & providers'

  generate_doc_and_deploy:
    needs: test
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/master'
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Setup Pages
        uses: actions/configure-pages@v2
      - name: Upload artifact
        uses: actions/upload-pages-artifact@v1
        with:
          path: 'docs/'
      - name: Deploy to GitHub Pages
        id: deployment
        uses: actions/deploy-pages@v1


Lint Moonstream backend

name: Lint Moonstream backend

on:
  pull_request:
    branches:
      - "main"
    paths:
      - "backend/**"
      - "!backend/deploy/**"

jobs:
  build:
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v2
      - name: Set up python
        uses: actions/setup-python@v2
        with:
          python-version: "3.8"
      - name: Upgrade pip and setuptools
        run: pip install --upgrade pip setuptools
      - name: Install test requirements
        working-directory: ./backend
        run: pip install -e .[dev]
      - name: Mypy type check
        working-directory: ./backend
        run: mypy moonstreamapi/
      - name: Black syntax check
        working-directory: ./backend
        run: black --check moonstreamapi/


Lint Moonstream crawlers

name: Lint Moonstream crawlers

on:
  pull_request:
    branches:
      - "main"
    paths:
      - "crawlers/**"
      - "!crawlers/deploy/**"

jobs:
  build:
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v2
      - name: Set up python
        uses: actions/setup-python@v2
        with:
          python-version: "3.8"
      - name: Upgrade pip and setuptools
        run: pip install --upgrade pip setuptools
      - name: Install test requirements
        working-directory: ./crawlers/mooncrawl
        run: pip install -e .[dev]
      - name: Mypy type check
        working-directory: ./crawlers/mooncrawl
        run: mypy mooncrawl/
      - name: Black syntax check
        working-directory: ./crawlers/mooncrawl
        run: black --check mooncrawl/


Lint Moonstream db

name: Lint Moonstream db

on:
  pull_request:
    branches:
      - "main"
    paths:
      - "db/**"
      - "!db/deploy/**"

jobs:
  build:
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v2
      - name: Set up python
        uses: actions/setup-python@v2
        with:
          python-version: "3.8"
      - name: Upgrade pip and setuptools
        run: pip install --upgrade pip setuptools
      - name: Install test requirements
        working-directory: ./db
        run: pip install -e .[dev]
      - name: Mypy type check
        working-directory: ./db
        run: mypy moonstreamdb/
      - name: Black syntax check
        working-directory: ./db
        run: black --check moonstreamdb/


Build Moonstream frontend

name: Build Moonstream frontend

on:
  pull_request:
    branches:
      - "main"
    paths:
      - "frontend/**"

jobs:
  build:
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v2
      - name: Set up node.js
        uses: actions/setup-node@v1
        with:
          node-version: "14.17.4"
      - name: Check build
        working-directory: ./frontend
        run: |
          yarn
          yarn build


Locust summary

name: Locust summary

on: [pull_request_target]

jobs:
  build:
    runs-on: ubuntu-20.04
    steps:
      - name: PR head repo
        id: head_repo_name
        run: |
          HEAD_REPO_NAME=$(jq -r '.pull_request.head.repo.full_name' "$GITHUB_EVENT_PATH")
          echo "PR head repo: $HEAD_REPO_NAME"
          echo "::set-output name=repo::$HEAD_REPO_NAME"
      - name: Checkout git repo
        uses: actions/checkout@v2
        with:
          repository: ${{ steps.head_repo_name.outputs.repo }}
          fetch-depth: 0
      - name: Install python
        uses: actions/setup-python@v2
        with:
          python-version: "3.8"
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip setuptools
          pip install bugout-locust
      - name: Generate and send Locust summary
        env:
          BUGOUT_SECRET: ${{ secrets.BUGOUT_SECRET }}
        run: |
          locust.github publish


name: Publish Moonstream Python client library

on:
  push:
    tags:
      - "clients/python/v*"

defaults:
  run:
    working-directory: clients/python

jobs:
  publish:
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: "3.9"
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -e .[distribute]
      - name: Build and publish
        env:
          TWINE_USERNAME: "__token__"
          TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN_MOONSTREAM }}
        run: |
          python setup.py sdist bdist_wheel
          twine upload dist/*


Release mooncrawl package

name: Release mooncrawl package

on:
  push:
    tags:
      - 'mooncrawl/v*'

defaults:
  run:
    working-directory: crawlers/mooncrawl

jobs:
  publish:
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: '3.8'
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -e .[distribute]
      - name: Build and publish
        env:
          TWINE_USERNAME: "__token__"
          TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN_MOONCRAWL }}
        run: |
          python setup.py sdist bdist_wheel
          twine upload dist/*


Release moonstreamapi package

name: Release moonstreamapi package

on:
  push:
    tags:
      - 'moonstreamapi/v*'

defaults:
  run:
    working-directory: backend

jobs:
  publish:
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: '3.8'
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -e .[distribute]
      - name: Build and publish
        env:
          TWINE_USERNAME: "__token__"
          TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN_MOONSTREAMAPI }}
        run: |
          python setup.py sdist bdist_wheel
          twine upload dist/*


Release moonstreamdb package

name: Release moonstreamdb package

on:
  push:
    tags:
      - 'moonstreamdb/v*'

defaults:
  run:
    working-directory: moonstreamdb

jobs:
  publish:
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: '3.8'
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -e .[distribute]
      - name: Build and publish
        env:
          TWINE_USERNAME: "__token__"
          TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN_MOONSTREAMDB }}
        run: |
          python setup.py sdist bdist_wheel
          twine upload dist/*


Release moonstreamdbv3 package

name: Release moonstreamdbv3 package

on:
  push:
    tags:
      - 'moonstreamdbv3/v*'

defaults:
  run:
    working-directory: moonstreamdb-v3

jobs:
  publish:
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: '3.8'
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -e .[distribute]
      - name: Build and publish
        env:
          TWINE_USERNAME: "__token__"
          TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN_MOONSTREAMDBV3 }}
        run: |
          python setup.py sdist bdist_wheel
          twine upload dist/*


Publish Moonstream Python types library

name: Publish Moonstream Python types library

on:
  push:
    tags:
      - "types/python/v*"

defaults:
  run:
    working-directory: types/python

jobs:
  publish:
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-python@v4
        with:
          python-version: "3.9"
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -e .[distribute]
      - name: Build and publish
        env:
          TWINE_USERNAME: "__token__"
          TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN_MOONSTREAM_TYPES }}
        run: |
          python setup.py sdist bdist_wheel
          twine upload dist/*


name: Linting and tests for the Moonstream Python client library

on:
  pull_request:
    branches:
      - "main"
    paths:
      - "clients/python/**"

jobs:
  build:
    runs-on: ubuntu-20.04
    steps:
      - uses: actions/checkout@v2
      - name: Set up python
        uses: actions/setup-python@v2
        with:
          python-version: "3.9"
      - name: Install test requirements
        working-directory: ./clients/python
        run: pip install -e .[dev]
      - name: Mypy type check
        working-directory: ./clients/python
        run: mypy moonstream/
      - name: Black syntax check
        working-directory: ./clients/python
        run: black --check moonstream/
      - name: Unit tests
        working-directory: ./clients/python
        run: python -m unittest discover -v
      - name: Check that versions are synchronized
        working-directory: ./clients/python
        run: |
          MOONSTREAM_CLIENT_VERSION=$(python -c "from moonstream.client import MOONSTREAM_CLIENT_VERSION; print(MOONSTREAM_CLIENT_VERSION)")
          SETUP_PY_VERSION=$(python setup.py --version)
          echo "Client version: $MOONSTREAM_CLIENT_VERSION"
          echo "setup.py version: $SETUP_PY_VERSION"
          test "$MOONSTREAM_CLIENT_VERSION" = "$SETUP_PY_VERSION"


integration-tests

name: integration-tests
on:
  push:
    branches:
      - "master"
      - "test-me/*"
  pull_request:
    branches:
      - "*"
  schedule:
    - cron: "0 7 * * 1" # Run every Monday at 7:00 UTC

defaults:
  run:
    shell: bash -el {0}

jobs:
  integration-test:
    if: github.event.pull_request.draft == false
    name: ${{ matrix.os }} integration tests
    runs-on: ${{ matrix.os }}-latest
    strategy:
      fail-fast: false
      matrix:
        os: [macos, ubuntu, windows]
        include:
          - os: macos
            os-short: osx
          - os: ubuntu
            os-short: linux
          - os: windows
            os-short: win
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Setup Conda and parcels
        uses: ./.github/actions/install-parcels
        with:
          environment-file: environment.yml
          environment-name: py3_parcels
      - name: Integration test
        run: |
          coverage run -m pytest -v -s --nbval-lax -k "not documentation" --html="${{ matrix.os }}_integration_test_report.html" --self-contained-html docs/examples
          coverage xml
      - name: Codecov
        uses: codecov/codecov-action@v3.1.1
        with:
          flags: integration-tests
      - name: Upload test results
        if: ${{ always() }} # Always run this step, even if tests fail
        uses: actions/upload-artifact@v3.1.2
        with:
          name: Integration test report
          path: ${{ matrix.os }}_integration_test_report.html


linting

name: linting
on:
  pull_request:
  push:
jobs:
  linting:
    name: Linting with flake8
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@master
      - name: Python setup
        run: |
          pip install wheel
          pip install flake8
      - name: flake8
        run: |
          python -m flake8 parcels
          python -m flake8 tests


Build and Upload to PyPI

name: Build and Upload to PyPI
on:
  release:
    types:
      - published
  push:
    tags:
      - "v*"
  workflow_dispatch:

jobs:
  build-artifacts:
    runs-on: ubuntu-latest
    if: github.repository == 'OceanParcels/parcels'
    steps:
      - uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - uses: actions/setup-python@v3
        name: Install Python
        with:
          python-version: 3.8

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          python -m pip install setuptools setuptools-scm wheel twine check-manifest

      - name: Build tarball and wheels
        run: |
          git clean -xdf
          git restore -SW .
          python -m build --sdist --wheel .

      - name: Check built artifacts
        run: |
          python -m twine check dist/*
          pwd
          if [ -f dist/parcels-0.0.0.tar.gz ]; then
            echo "❌ INVALID VERSION NUMBER"
            exit 1
          else
            echo "✅ Looks good"
          fi
      - uses: actions/upload-artifact@v3
        with:
          name: releases
          path: dist

  test-built-dist:
    needs: build-artifacts
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-python@v3
        name: Install Python
        with:
          python-version: 3.8
      - uses: actions/download-artifact@v3
        with:
          name: releases
          path: dist
      - name: List contents of built dist
        run: |
          ls -ltrh
          ls -ltrh dist

      - name: Verify the built dist/wheel is valid
        if: github.event_name == 'push'
        run: |
          python -m pip install --upgrade pip
          python -m pip install dist/parcels*.whl

      - name: Publish package to TestPyPI
        if: github.event_name == 'push'
        uses: pypa/gh-action-pypi-publish@v1.5.0
        with:
          user: __token__
          password: ${{ secrets.PARCELS_PYPI_TEST_TOKEN }}
          repository_url: https://test.pypi.org/legacy/
          verbose: true

  upload-to-pypi:
    needs: test-built-dist
    if: github.event_name == 'release'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v3
        with:
          name: releases
          path: dist
      - name: Publish package to PyPI
        uses: pypa/gh-action-pypi-publish@v1.5.0
        with:
          user: __token__
          password: ${{ secrets.PARCELS_PYPI_PROD_TOKEN }}
          verbose: true

  test-pypi-release:
    needs: upload-to-pypi
    runs-on: ubuntu-latest
    steps:
      - uses: conda-incubator/setup-miniconda@v3
        with:
          activate-environment: parcels
          python-version: 3.8
          channels: conda-forge
      - run: conda install -c conda-forge c-compiler pip
      - run: pip install parcels --no-cache
      - run: curl https://raw.githubusercontent.com/OceanParcels/parcels/master/docs/examples/example_peninsula.py > example_peninsula.py
      - run: python example_peninsula.py


unit-tests

name: unit-tests
on:
  push:
    branches:
      - "master"
      - "test-me/*"
  pull_request:
    branches:
      - "*"
  schedule:
    - cron: "0 7 * * 1" # Run every Monday at 7:00 UTC

defaults:
  run:
    shell: bash -el {0}

jobs:
  unit-test:
    name: Unittesting on ${{ matrix.os }} with python latest
    runs-on: ${{ matrix.os }}-latest
    strategy:
      fail-fast: false
      matrix:
        os: [macos, ubuntu, windows]
        include: # TODO check if these can go?
          - os: macos
            os-short: osx
          - os: ubuntu
            os-short: linux
          - os: windows
            os-short: win
    steps:
      - name: Checkout
        uses: actions/checkout@v3
      - name: Setup Conda and parcels
        uses: ./.github/actions/install-parcels
        with:
          environment-file: environment.yml
          environment-name: py3_parcels
      - name: Unit test
        run: |
          coverage run -m pytest -v -s --html=${{ matrix.os }}_unit_test_report.html --self-contained-html tests
          coverage xml
      - name: Codecov
        uses: codecov/codecov-action@v3.1.1
        with:
          flags: unit-tests
      - name: Upload test results
        if: ${{ always() }} # Always run this step, even if tests fail
        uses: actions/upload-artifact@v3.1.2
        with:
          name: Unittest report
          path: ${{ matrix.os }}_unit_test_report.html


Auto Assign

name: Auto Assign
on:
  issues:
    types: [opened]
  pull_request:
    types: [opened]
jobs:
  run:
    runs-on: ubuntu-latest
    permissions:
      issues: write
      pull-requests: write
    steps:
    - name: 'Auto-assign issue'
      uses: pozil/auto-assign-issue@v1
      with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          assignees: dkmstr
          numOfAssignee: 1


Dependency Review

# Dependency Review Action
#
# This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging.
#
# Source repository: https://github.com/actions/dependency-review-action
# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement
name: 'Dependency Review'
on: [pull_request]

permissions:
  contents: read

jobs:
  dependency-review:
    runs-on: ubuntu-latest
    steps:
      - name: 'Checkout Repository'
        uses: actions/checkout@v3
      - name: 'Dependency Review'
        uses: actions/dependency-review-action@v3


CI

name: CI

on:
    push:
        branches:
            - main
    pull_request:
        branches:
            - main

    # Allows you to run this workflow manually from the Actions tab
    workflow_dispatch:

jobs:

    Linting:
        runs-on: ubuntu-latest
        strategy:
            matrix:
                python-version: ['3.8', '3.9', '3.10']

        steps:
            -   uses: actions/checkout@v2
            -   name: Set up Python ${{ matrix.python-version }}
                uses: actions/setup-python@v2
                with:
                    python-version: ${{ matrix.python-version }}

            -   uses: actions/cache@v2
                with:
                    path: ~/.cache/pip
                    key: ${{ runner.os }}-pip

            -   name: Install Dependencies
                run: |
                    python -m pip install --upgrade pip
                    make dev
                    pip install IPython==8.12.0

            -   name: Lint with isort, black, docformatter, flake8
                run: |
                    make lint

    # Documentation:
    #     needs: Linting
    #     runs-on: ubuntu-latest
    #     strategy:
    #         matrix:
    #             python-version: ['3.8', '3.9', '3.10']

    #     steps:
    #         -   uses: actions/checkout@v2
    #         -   name: Set up Python ${{ matrix.python-version }}
    #             uses: actions/setup-python@v2
    #             with:
    #                 python-version: ${{ matrix.python-version }}

    #         -   uses: actions/cache@v2
    #             with:
    #                 path: ~/.cache/pip
    #                 key: ${{ runner.os }}-pip

    #         -   name: Install Dependencies
    #             run: |
    #                 python -m pip install --upgrade pip
    #                 make dev

    #         -   name: Generate Docs
    #             run: |
    #                 make docs

    Interactive-Setup:
        # needs: Documentation
        runs-on: ${{ matrix.os }}
        strategy:
            matrix:
                # macOS automatically imports homebrew. So this will be used for setup.
                os: [macos-latest, ubuntu-latest]
                python-version: ['3.10']

        steps:
            -   uses: actions/checkout@v2

            -   name: Set up Python ${{ matrix.python-version }}
                uses: actions/setup-python@v2
                with:
                    python-version: ${{ matrix.python-version }}

            -   uses: actions/cache@v2
                with:
                    path: ~/.cache/pip
                    key: ${{ runner.os }}-pip

            -   name: Install Interactive Dependencies
                run: |
                    pip install -e ".[interactive]"

            -   name: Test interactive install
                run: |
                    make test-interactive-install

    Build-Basic:
        # needs: Documentation
        runs-on: ${{ matrix.os }}
        strategy:
            matrix:
                os: [ubuntu-latest, macos-latest]
                python-version: ['3.8', '3.9', '3.10']

        steps:
            -   uses: actions/checkout@v2

            -   name: Set up Python ${{ matrix.python-version }}
                uses: actions/setup-python@v2
                with:
                    python-version: ${{ matrix.python-version }}

            -   uses: actions/cache@v2
                with:
                    path: ~/.cache/pip
                    key: ${{ runner.os }}-pip

            -   name: Install Bare Dependencies
                run: |
                    pip install -e .

            -   name: Test basic importing
                run: |
                    make test-basic

    Build:
        if:
            contains('
            refs/heads/main
            ', github.event.pull_request.base.ref)
        needs: Build-Basic
        runs-on: ${{ matrix.os }}
        strategy:
            matrix:
                os: [ubuntu-latest] #, macos-latest]
                python-version: ['3.8', '3.9', '3.10']

        steps:
            -   uses: actions/checkout@v2

            -   name: Set up Python ${{ matrix.python-version }}
                uses: actions/setup-python@v2
                with:
                    python-version: ${{ matrix.python-version }}

            -   uses: actions/cache@v2
                with:
                    path: ~/.cache/pip
                    key: ${{ runner.os }}-pip

            -   name: Install Dependencies
                run: |
                    pip install -e ".[all]"

            -   name: Test with pytest
                run: |
                    make test-cov

            -   name: Upload to codecov.io
                uses: codecov/codecov-action@v1
                with:
                    file: ./coverage.xml
                    flags: unittests
                    name: codecov-umbrella
                    fail_ci_if_error: true

#            -   name: Publish distribution 📦 to PyPI
#                if: startsWith(github.event.ref, 'refs/tags')
#                env:
#                    PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
#                run: |
#                    poetry config pypi-token.pypi $PYPI_TOKEN
#                    poetry publish --build


GitHub Pages

# Github Pages action
# Adapted from https://github.com/marketplace/actions/github-pages-action
name: GitHub Pages

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-22.04
    permissions:
      contents: write
    concurrency:
      group: ${{ github.workflow }}-${{ github.ref }}
    steps:
      - uses: actions/checkout@v3

      - name: Setup Python
        uses: actions/setup-python@v3
        with:
          python-version: '3.9'

      - name: Upgrade pip
        run: |
          # install pip=>20.1 to use "pip cache dir"
          python3 -m pip install --upgrade pip

      - name: Get pip cache dir
        id: pip-cache
        run: echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT

      - name: Cache dependencies
        uses: actions/cache@v3
        with:
          path: ${{ steps.pip-cache.outputs.dir }}
          key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
          restore-keys: |
            ${{ runner.os }}-pip-

      - name: Install dependencies
        run: |
          python3 -m pip install -e '.[dev]'
          python3 -m pip install -r docs/requirements.txt
          python3 -m pip install seaborn torchvision
          make dev
          make website-install

      - name: Build website
        run: |
          make website -B
          echo "meerkat.wiki" > ./website/build/CNAME

      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
        if: ${{ github.ref == 'refs/heads/main' }}
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_branch: gh-pages
          publish_dir: ./website/build