Build Docker images

name: Build Docker images
on:
  push:
    branches:
      - main
      - dev
  pull_request:
    branches:
      - main
      - dev
  release:
    types:
      - published

permissions: {}

jobs:
  build:
    permissions:
      contents: read #  to fetch code (actions/checkout)
      packages: write #  to push docker image

    name: Build image
    runs-on: ubuntu-latest
    steps:
      - name: Checkout main
        uses: actions/checkout@v2
      - name: Run the build
        run: |
          set -ex
          # use that here since the variable are not present before start, so can't be in env
          export LOGIN=$GITHUB_REPOSITORY_OWNER

          echo $PASSWORD | docker login $REGISTRY -u $LOGIN --password-stdin

          for i in docker/* ; do
              CONTAINER=$(basename $i)
              echo "Building $CONTAINER"
              export IMAGE=$LOGIN/augur_$CONTAINER
              DOCKERFILE=${i}/Dockerfile

              docker build . -f $DOCKERFILE --tag $REGISTRY/$IMAGE:latest
              if [[ $GITHUB_EVENT_NAME == 'release' ]]; then
                TAG=$(basename $GITHUB_REF)
                docker tag $REGISTRY/$IMAGE:latest $REGISTRY/$IMAGE:$TAG
                docker push $REGISTRY/$IMAGE:latest
                docker push $REGISTRY/$IMAGE:$TAG
              elif [[ $GITHUB_EVENT_NAME == 'push' ]]; then
                docker tag $REGISTRY/$IMAGE:latest $REGISTRY/$IMAGE:devel-latest
                docker push $REGISTRY/$IMAGE:devel-latest
              fi
          done
        env:
          REGISTRY: ghcr.io
          PASSWORD: ${{ secrets.GITHUB_TOKEN }}


run-linting-checks

name: "run-linting-checks"
on:
  pull_request:
    branches: [main, dev]

jobs:
  run-pylint:
    name: runner / pylint
    permissions: write-all
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: dciborow/action-pylint@0.1.0
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          reporter: github-pr-review
          level: warning
          glob_pattern: "**/*.py"
          filter_mode: "file"

  misspell:
    name: runner / misspell
    runs-on: ubuntu-latest
    steps:
      - name: Highlight any misspellings in changes.
        uses: actions/checkout@v4
      - name: misspell
        uses: reviewdog/action-misspell@v1
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          locale: "US"


Merge main into dev

name: Merge main into dev

on:
  push:
    branches:
      - main

jobs:
  update-dev:
    permissions: write-all
    name: update-dev
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - run: |
          git config user.name 'GitHub Actions'
          git config user.email 'actions@users.noreply.github.com'
          git checkout dev
          git merge main
          echo "Done with merge"
      - name: Push to dev
        uses: CasperWA/push-protected@v2
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          branch: dev


Mark stale issues and pull requests

name: Mark stale issues and pull requests

on:
  schedule:
    # Every day at 12:00 pm
    - cron: 0 0 * * *

jobs:
  stale:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/stale@v9
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          stale-issue-message: This issue has been automatically marked as stale. You have a week to explain why you believe this is an error.
          stale-pr-message: This PR has been automatically marked as stale. You have a week to explain why you believe this is an error.
          stale-issue-label: no-issue-activity
          stale-pr-label: no-pr-activity


Develop build

name: Develop build

on:
  pull_request:
    branches:
      - main
    paths:
      - images/production/**
      - overrides/**
      - tests/**
      - compose.yaml
      - docker-bake.hcl
      - example.env
      - .github/workflows/build_develop.yml

  schedule:
    # Every day at 12:00 pm
    - cron: 0 0 * * *

  workflow_dispatch:

jobs:
  build:
    uses: ./.github/workflows/docker-build-push.yml
    with:
      repo: erpnext
      version: develop
      push: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }}
      python_version: 3.11.6
      node_version: 18.18.2
    secrets:
      DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
      DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}


Stable build

name: Stable build

on:
  pull_request:
    branches:
      - main
    paths:
      - images/production/**
      - overrides/**
      - tests/**
      - compose.yaml
      - docker-bake.hcl
      - example.env
      - .github/workflows/build_stable.yml

  push:
    branches:
      - main
    paths:
      - images/production/**
      - overrides/**
      - tests/**
      - compose.yaml
      - docker-bake.hcl
      - example.env

  # Triggered from frappe/frappe and frappe/erpnext on releases
  repository_dispatch:

  workflow_dispatch:

jobs:
  v14:
    uses: ./.github/workflows/docker-build-push.yml
    with:
      repo: erpnext
      version: "14"
      push: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }}
      python_version: 3.10.13
      node_version: 16.20.2
    secrets:
      DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
      DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}

  v15:
    uses: ./.github/workflows/docker-build-push.yml
    with:
      repo: erpnext
      version: "15"
      push: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }}
      python_version: 3.11.6
      node_version: 18.18.2
    secrets:
      DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
      DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}

  update_versions:
    name: Update example.env and pwd.yml
    runs-on: ubuntu-latest
    if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }}
    needs: v15

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.10"

      - name: Get latest versions
        run: python3 ./.github/scripts/get_latest_tags.py --repo erpnext --version 15

      - name: Update
        run: |
          python3 ./.github/scripts/update_example_env.py
          python3 ./.github/scripts/update_pwd.py

      - name: Push
        run: |
          git config --global user.name github-actions
          git config --global user.email github-actions@github.com
          git add example.env pwd.yml
          if [ -z "$(git status --porcelain)" ]; then
            echo "versions did not change, exiting."
            exit 0
          else
            echo "version changed, pushing changes..."
            git commit -m "chore: Update example.env"
            git pull --rebase
            git push origin main
          fi

  release_helm:
    name: Release Helm
    runs-on: ubuntu-latest
    if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }}
    needs: v15

    steps:
      - name: Setup deploy key
        uses: webfactory/ssh-agent@v0.8.0
        with:
          ssh-private-key: ${{ secrets.HELM_DEPLOY_KEY }}

      - name: Setup Git Credentials
        run: |
          git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git config --global user.name "github-actions[bot]"

      - name: Release
        run: |
          git clone git@github.com:frappe/helm.git && cd helm
          pip install -r release_wizard/requirements.txt
          ./release_wizard/wizard 15 patch --remote origin --ci


Lint

name: Lint

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

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.10.6"

      # For shfmt pre-commit hook
      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: "^1.14"

      - name: Install pre-commit
        run: pip install -U pre-commit

      - name: Lint
        run: pre-commit run --color=always --all-files
        env:
          GO111MODULE: on


Autoupdate pre-commit hooks

name: Autoupdate pre-commit hooks

on:
  schedule:
    # Every day at 7 am
    - cron: 0 7 * * *

jobs:
  pre-commit-autoupdate:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Update pre-commit hooks
        uses: vrslev/pre-commit-autoupdate@v1.0.0

      - name: Create Pull Request
        uses: peter-evans/create-pull-request@v6
        with:
          branch: pre-commit-autoupdate
          title: "chore(deps): Update pre-commit hooks"
          commit-message: "chore(deps): Update pre-commit hooks"
          body: Update pre-commit hooks
          labels: dependencies,development
          delete-branch: True


Bench

name: Bench

on:
  pull_request:
    branches:
      - main
    paths:
      - images/bench/**
      - docker-bake.hcl
      - .github/workflows/build_bench.yml

  schedule:
    # Every day at 12:00 pm
    - cron: 0 0 * * *

  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup QEMU
        uses: docker/setup-qemu-action@v3
        with:
          image: tonistiigi/binfmt:latest
          platforms: all

      - name: Setup Buildx
        uses: docker/setup-buildx-action@v3

      - name: Set Environment Variables
        run: cat example.env | grep -o '^[^#]*' >> "$GITHUB_ENV"

      - name: Get Bench Latest Version
        run: echo "LATEST_BENCH_RELEASE=$(curl -s 'https://api.github.com/repos/frappe/bench/releases/latest' | jq -r '.tag_name')" >> "$GITHUB_ENV"

      - name: Build and test
        uses: docker/bake-action@v4.6.0
        with:
          targets: bench-test

      - name: Login
        if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }}
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Push
        if: ${{ github.repository == 'frappe/frappe_docker' && github.event_name != 'pull_request' }}
        uses: docker/bake-action@v4.6.0
        with:
          targets: bench
          push: true
          set: "*.platform=linux/amd64,linux/arm64"


Build

name: Build

on:
  workflow_call:
    inputs:
      repo:
        required: true
        type: string
        description: "'erpnext' or 'frappe'"
      version:
        required: true
        type: string
        description: "Major version, git tags should match 'v{version}.*'; or 'develop'"
      push:
        required: true
        type: boolean
      python_version:
        required: true
        type: string
        description: Python Version
      node_version:
        required: true
        type: string
        description: NodeJS Version
    secrets:
      DOCKERHUB_USERNAME:
        required: true
      DOCKERHUB_TOKEN:
        required: true

jobs:
  build:
    name: Build
    runs-on: ubuntu-latest
    services:
      registry:
        image: registry:2
        ports:
          - 5000:5000

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup Buildx
        uses: docker/setup-buildx-action@v3
        with:
          driver-opts: network=host

      - name: Get latest versions
        run: python3 ./.github/scripts/get_latest_tags.py --repo ${{ inputs.repo }} --version ${{ inputs.version }}

      - name: Set build args
        run: |
          echo "PYTHON_VERSION=${{ inputs.python_version }}" >> "$GITHUB_ENV"
          echo "NODE_VERSION=${{ inputs.node_version }}" >> "$GITHUB_ENV"

      - name: Build
        uses: docker/bake-action@v4.6.0
        with:
          push: true
        env:
          REGISTRY_USER: localhost:5000/frappe

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.10"

      - name: Install dependencies
        run: |
          python -m venv venv
          venv/bin/pip install -r requirements-test.txt

      - name: Test
        run: venv/bin/pytest --color=yes

      - name: Login
        if: ${{ inputs.push }}
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Push
        if: ${{ inputs.push }}
        uses: docker/bake-action@v4.6.0
        with:
          push: true


Audit

name: Audit

on:
  schedule:
    - cron: '30 2 * * *'

jobs:
  audit:
    name: Audit
    runs-on: ubuntu-22.04
    timeout-minutes: 10

    strategy:
      fail-fast: false
      matrix:
        branch:
          - '1.6'
          - '1.8'
          - '1.9'
          - '1.10'
          - '1.11'

    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ matrix.branch }}
          token: ${{ secrets.GOPASS_CI_GITHUB_TOKEN }}

      - uses: camptocamp/initialise-gopass-summon-action@v2
        with:
          ci-gpg-private-key: ${{secrets.CI_GPG_PRIVATE_KEY}}
          github-gopass-ci-token: ${{secrets.GOPASS_CI_GITHUB_TOKEN}}

      - run: python3 -m venv ~/.venv
      - run: ~/.venv/bin/pip install --pre c2cciutils[audit]
      - run: python3 -m pip install --pre c2cciutils[audit]

      - name: Check .tool-versions file existence
        id: tool-versions
        uses: andstor/file-existence-action@v3
        with:
          files: .tool-versions
      - uses: asdf-vm/actions/install@v3
        if: steps.tool-versions.outputs.files_exists == 'true'
      - run: cat /tmp/python-build.*.log
        if: failure()
      - run: python --version

      - name: Snyk audit
        run: ~/.venv/bin/c2cciutils-audit --branch=${{ matrix.branch }}
        env:
          GITHUB_TOKEN: ${{ secrets.GOPASS_CI_GITHUB_TOKEN }}
      - name: Debug Snyk audit
        run: ~/.venv/bin/c2cciutils-audit --branch=${{ matrix.branch }} || true
        env:
          GITHUB_TOKEN: ${{ secrets.GOPASS_CI_GITHUB_TOKEN }}
          DEBUG: '*snyk*'
        if: failure()
      - name: Check ci/dpkg-versions.yaml file existence
        id: dpkg-versions
        uses: andstor/file-existence-action@v3
        with:
          files: ci/dpkg-versions.yaml
      - name: Update dpkg packages versions
        run: ~/.venv/bin/c2cciutils-docker-versions-update --branch=${{ matrix.branch }}
        env:
          GITHUB_TOKEN: ${{ secrets.GOPASS_CI_GITHUB_TOKEN }}
        if: steps.dpkg-versions.outputs.files_exists == 'true'


Backport

name: Backport

on:
  pull_request:
    types:
      - closed
      - labeled

env:
  HAS_SECRETS: ${{ secrets.HAS_SECRETS }}

jobs:
  backport:
    name: Backport
    runs-on: ubuntu-22.04
    timeout-minutes: 5

    steps:
      - uses: camptocamp/backport-action@master
        with:
          token: ${{ secrets.GOPASS_CI_GITHUB_TOKEN }}
        if: env.HAS_SECRETS == 'HAS_SECRETS'


Clean docker hub tags

name: Clean docker hub tags

on:
  delete:
  pull_request:
    types:
      - closed

jobs:
  clean:
    name: Clean docker hub tags
    runs-on: ubuntu-22.04
    timeout-minutes: 5

    steps:
      - uses: actions/checkout@v4

      - uses: camptocamp/initialise-gopass-summon-action@v2
        with:
          ci-gpg-private-key: ${{secrets.CI_GPG_PRIVATE_KEY}}
          github-gopass-ci-token: ${{secrets.GOPASS_CI_GITHUB_TOKEN}}
          patterns: docker

      - run: echo "${HOME}/.local/bin" >> ${GITHUB_PATH}
      - run: python3 -m pip install --user --requirement=ci/requirements.txt

      - name: Clean Docker hub tags
        run: c2cciutils-clean


Continuous integration

name: Continuous integration

on:
  push:
    branches:
      - master
      - '[0-9]+.[0-9]+'
    tags:
      - '*'
  pull_request:

env:
  HAS_SECRETS: ${{ secrets.HAS_SECRETS }}

jobs:
  main:
    name: Continuous integration
    runs-on: ubuntu-22.04
    timeout-minutes: 10
    if: "!startsWith(github.event.head_commit.message, '[skip ci] ')"

    steps:
      - run: docker system prune --all --force
      - uses: actions/checkout@v4

      - uses: camptocamp/initialise-gopass-summon-action@v2
        with:
          ci-gpg-private-key: ${{secrets.CI_GPG_PRIVATE_KEY}}
          github-gopass-ci-token: ${{secrets.GOPASS_CI_GITHUB_TOKEN}}
          patterns: pypi
        if: env.HAS_SECRETS == 'HAS_SECRETS'

      - run: echo "${HOME}/.local/bin" >> ${GITHUB_PATH}
      - run: python3 -m pip install --user --requirement=ci/requirements.txt

      - uses: actions/cache@v4
        with:
          path: ~/.cache/pre-commit
          key: pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}
          restore-keys: "pre-commit-${{ hashFiles('.pre-commit-config.yaml') }}\npre-commit-"
      - run: pre-commit run --all-files
      - run: git diff --exit-code --patch > /tmp/pre-commit.patch || true
        if: failure()
      - uses: actions/upload-artifact@v4
        with:
          name: Apply pre-commit fix.patch
          path: /tmp/pre-commit.patch
          retention-days: 1
        if: failure()
      - name: Print environment information
        run: c2cciutils-env

      - name: Applications checks
        run: make checks

      - name: Logs
        run: c2cciutils-docker-logs
        if: always()

      - name: Publish
        run: c2cciutils-publish
        if: |
          env.HAS_SECRETS == 'HAS_SECRETS'
          && github.event_name == 'push'
          && ( github.ref_type == 'tag' || github.ref_name == 'master' )
      - run: git diff --exit-code --patch > /tmp/dpkg-versions.patch || true
        if: failure()
      - uses: actions/upload-artifact@v4
        with:
          name: Update dpkg versions list.patch
          path: /tmp/dpkg-versions.patch
          retention-days: 1
        if: failure()


name: Auto reviews, merge and close pull requests

on:
  pull_request:
    types:
      - opened
      - reopened

jobs:
  auto-merge:
    name: Auto reviews pull requests from bots
    runs-on: ubuntu-22.04
    timeout-minutes: 5

    steps:
      - name: Print event
        run: echo "${GITHUB}" | jq
        env:
          GITHUB: ${{ toJson(github) }}
      - name: Print context
        uses: actions/github-script@v7
        with:
          script: |-
            console.log(context);
      - name: Auto reviews GHCI updates
        uses: actions/github-script@v7
        with:
          script: |-
            github.rest.pulls.createReview({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: context.payload.pull_request.number,
              event: 'APPROVE',
            })
        if: |-
          startsWith(github.head_ref, 'ghci/audit/')
          && (github.event.pull_request.user.login == 'geo-ghci-test[bot]'
            || github.event.pull_request.user.login == 'geo-ghci-int[bot]'
            || github.event.pull_request.user.login == 'geo-ghci[bot]')
      - name: Auto reviews Renovate updates
        uses: actions/github-script@v7
        with:
          script: |-
            github.rest.pulls.createReview({
              owner: context.repo.owner,
              repo: context.repo.repo,
              pull_number: context.payload.pull_request.number,
              event: 'APPROVE',
            })
        if: |-
          github.event.pull_request.user.login == 'renovate[bot]'


Downstream Tests

name: Downstream Tests

on:
  push:
  pull_request:

jobs:
  nbclassic:
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Base Setup
        uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1

      - name: Test nbclassic
        uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1
        with:
          package_name: nbclassic
          test_command: pip install pytest-jupyter[server] && pytest -vv -raXxs -W default --durations 10 --color=yes

      - name: Test run nbclassic
        run: |
          pip install nbclassic
          pip install --force-reinstall "."

          # Make sure we can start and kill the nbclassic server
          jupyter nbclassic --no-browser &
          TASK_PID=$!
          # Make sure the task is running
          ps -p $TASK_PID || exit 1
          sleep 5
          kill $TASK_PID
          wait $TASK_PID

  notebook:
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Base Setup
        uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1

      - name: Test notebook
        uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1
        with:
          package_name: notebook
          package_download_extra_args: "--pre"
          test_command: pip install pytest-jupyter[server] && pytest -vv -raXxs -W default --durations 10 --color=yes

  jupyterlab_server:
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Base Setup
        uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1

      - run: pip install pytest-jupyter[server]

      - name: Test jupyterlab_server
        uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1
        with:
          package_name: jupyterlab_server
          test_command: pip install pytest-jupyter[server] && pytest -vv -raXxs -W default --durations 10 --color=yes

  jupyterlab:
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Base Setup
        uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1

      - name: Test jupyterlab
        uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1
        with:
          package_name: jupyterlab
          test_command: "python -m jupyterlab.browser_check --no-browser-test"

  jupyter_server_terminals:
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Base Setup
        uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1

      - name: Test jupyter_server_terminals
        uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1
        with:
          test_command: pip install pytest-jupyter[server] && pytest -vv -raXxs -W default --durations 10 --color=yes
          package_name: jupyter_server_terminals

  jupytext:
    runs-on: ubuntu-latest
    timeout-minutes: 10

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Base Setup
        uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1

      - name: Test jupytext
        uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1
        with:
          package_name: jupytext
          test_command: pip install pytest-jupyter[server] gitpython pre-commit && python -m ipykernel install --name jupytext-dev --user && pytest -vv -raXxs -W default --durations 10 --ignore=tests/functional/others --color=yes

  downstream_check: # This job does nothing and is only used for the branch protection
    if: always()
    needs:
      - jupyterlab
      - jupyter_server_terminals
      - jupyterlab_server
      - notebook
      - nbclassic
      - jupytext
    runs-on: ubuntu-latest
    steps:
      - name: Decide whether the needed jobs succeeded or failed
        uses: re-actors/alls-green@release/v1
        with:
          jobs: ${{ toJSON(needs) }}


Enforce PR label

name: Enforce PR label

on:
  pull_request:
    types: [labeled, unlabeled, opened, edited, synchronize]
jobs:
  enforce-label:
    runs-on: ubuntu-latest
    permissions:
      pull-requests: write
    steps:
      - name: enforce-triage-label
        uses: jupyterlab/maintainer-tools/.github/actions/enforce-label@v1


Jupyter Server Tests

name: Jupyter Server Tests
on:
  push:
    branches: ["main"]
  pull_request:
  schedule:
    - cron: "0 8 * * *"

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

jobs:
  build:
    runs-on: ${{ matrix.os }}
    timeout-minutes: 20
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        python-version: ["3.8", "3.11"]
        include:
          - os: windows-latest
            python-version: "3.9"
          - os: ubuntu-latest
            python-version: "pypy-3.8"
          - os: macos-latest
            python-version: "3.10"
          - os: ubuntu-latest
            python-version: "3.12"
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Base Setup
        uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
      - name: Install nbconvert dependencies on Linux
        if: startsWith(runner.os, 'Linux')
        run: |
          sudo apt-get update
          sudo apt-get install texlive-plain-generic inkscape texlive-xetex
          sudo apt-get install xvfb x11-utils libxkbcommon-x11-0
          # pandoc is not up to date in the ubuntu repos, so we install directly
          wget https://github.com/jgm/pandoc/releases/download/3.1.2/pandoc-3.1.2-1-amd64.deb && sudo dpkg -i pandoc-3.1.2-1-amd64.deb
      - name: Run the tests on posix
        if: ${{ !startsWith(matrix.python-version, 'pypy') && !startsWith(matrix.os, 'windows') }}
        run: hatch run cov:test --cov-fail-under 75 || hatch -v run test:test --lf
      - name: Run the tests on pypy
        if: ${{ startsWith(matrix.python-version, 'pypy') }}
        run: hatch run test:nowarn || hatch -v run test:nowarn --lf
      - name: Run the tests on windows
        if: ${{ startsWith(matrix.os, 'windows') }}
        run: hatch run cov:nowarn -s || hatch -v run cov:nowarn --lf
      - uses: jupyterlab/maintainer-tools/.github/actions/upload-coverage@v1

  test_docs:
    name: Test Docs
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
      - name: Install Dependencies
        run: |
          sudo apt-get update
          sudo apt-get install enchant-2  # for spell checking
      - name: Build API docs
        run: |
          hatch -v run docs:api
          # If this fails run `hatch run docs:api` locally
          # and commit.
          git status --porcelain
          git status -s | grep "A" && exit 1
          git status -s | grep "M" && exit 1
          echo "API docs done"
      - run: hatch -v run docs:build

  test_lint:
    name: Test Lint
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
      - name: Run Linters
        run: |
          hatch -v run typing:test
          hatch fmt
          pipx run interrogate -v .
          pipx run doc8 --max-line-length=200  --ignore-path=docs/source/other/full-config.rst
          npm install -g eslint
          eslint .

  test_examples:
    name: Test Examples
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
      - name: Install the Python dependencies for the examples
        run: |
          pip install -e ".[test]"
          cd examples/simple && pip install -e .
      - name: Run the tests for the examples
        run: |
          python -m pytest examples/simple

  test_minimum_versions:
    name: Test Minimum Versions
    timeout-minutes: 20
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
        with:
          dependency_type: minimum
      - name: Run the unit tests
        run: |
          hatch -vv run test:nowarn || hatch -v run test:nowarn --lf

  test_prereleases:
    name: Test Prereleases
    runs-on: ubuntu-latest
    timeout-minutes: 20
    steps:
      - uses: actions/checkout@v4
      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
        with:
          dependency_type: pre
      - name: Run the tests
        run: |
          hatch run test:nowarn || hatch -v run test:nowarn --lf

  make_sdist:
    name: Make SDist
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4
      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
      - uses: jupyterlab/maintainer-tools/.github/actions/make-sdist@v1

  test_sdist:
    runs-on: ubuntu-latest
    needs: [make_sdist]
    name: Install from SDist and Test
    timeout-minutes: 20
    steps:
      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
      - uses: jupyterlab/maintainer-tools/.github/actions/test-sdist@v1
        with:
          package_spec: -vv .
          test_command: hatch run test:test || hatch -v run test:test --lf

  check_release:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Base Setup
        uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
      - name: Install Dependencies
        run: |
          pip install -e .
      - name: Check Release
        uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2
        with:
          token: ${{ secrets.GITHUB_TOKEN }}

  check_links:
    name: Check Links
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
      - uses: actions/checkout@v4
      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
      - uses: jupyterlab/maintainer-tools/.github/actions/check-links@v1

  integration_check:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest]
        python-version: ["3.8", "3.9", "3.10", "3.11"]
    steps:
      - uses: actions/checkout@v4
      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
      - name: Run the tests
        run: hatch -v run cov:integration
      - uses: jupyterlab/maintainer-tools/.github/actions/upload-coverage@v1

  integration_check_pypy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1
        with:
          python_version: "pypy-3.8"
      - name: Run the tests
        run: hatch -v run test:nowarn --integration_tests=true

  coverage:
    runs-on: ubuntu-latest
    needs:
      - integration_check
      - build
    steps:
      - uses: actions/checkout@v4
      - uses: jupyterlab/maintainer-tools/.github/actions/report-coverage@v1
        with:
          fail_under: 80

  tests_check: # This job does nothing and is only used for the branch protection
    if: always()
    needs:
      - coverage
      - integration_check_pypy
      - test_docs
      - test_lint
      - test_examples
      - test_minimum_versions
      - test_prereleases
      - check_links
      - check_release
      - test_sdist
    runs-on: ubuntu-latest
    steps:
      - name: Decide whether the needed jobs succeeded or failed
        uses: re-actors/alls-green@release/v1
        with:
          jobs: ${{ toJSON(needs) }}


Step 1: Prep Release

name: "Step 1: Prep Release"
on:
  workflow_dispatch:
    inputs:
      version_spec:
        description: "New Version Specifier"
        default: "next"
        required: false
      branch:
        description: "The branch to target"
        required: false
      post_version_spec:
        description: "Post Version Specifier"
        required: false
      silent:
        description: "Set a placeholder in the changelog and don't publish the release."
        required: false
        type: boolean
      since:
        description: "Use PRs with activity since this date or git reference"
        required: false
      since_last_stable:
        description: "Use PRs with activity since the last stable git tag"
        required: false
        type: boolean
jobs:
  prep_release:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1

      - name: Prep Release
        id: prep-release
        uses: jupyter-server/jupyter_releaser/.github/actions/prep-release@v2
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          version_spec: ${{ github.event.inputs.version_spec }}
          silent: ${{ github.event.inputs.silent }}
          post_version_spec: ${{ github.event.inputs.post_version_spec }}
          target: ${{ github.event.inputs.target }}
          branch: ${{ github.event.inputs.branch }}
          since: ${{ github.event.inputs.since }}
          since_last_stable: ${{ github.event.inputs.since_last_stable }}

      - name: "** Next Step **"
        run: |
          echo "Optional): Review Draft Release: ${{ steps.prep-release.outputs.release_url }}"


Step 2: Publish Release

name: "Step 2: Publish Release"
on:
  workflow_dispatch:
    inputs:
      branch:
        description: "The target branch"
        required: false
      release_url:
        description: "The URL of the draft GitHub release"
        required: false
      steps_to_skip:
        description: "Comma separated list of steps to skip"
        required: false

jobs:
  publish_release:
    runs-on: ubuntu-latest
    environment: release
    permissions:
      id-token: write
    steps:
      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1

      - uses: actions/create-github-app-token@v1
        id: app-token
        with:
          app-id: ${{ vars.APP_ID }}
          private-key: ${{ secrets.APP_PRIVATE_KEY }}

      - name: Populate Release
        id: populate-release
        uses: jupyter-server/jupyter_releaser/.github/actions/populate-release@v2
        with:
          token: ${{ steps.app-token.outputs.token }}
          branch: ${{ github.event.inputs.branch }}
          release_url: ${{ github.event.inputs.release_url }}
          steps_to_skip: ${{ github.event.inputs.steps_to_skip }}

      - name: Finalize Release
        id: finalize-release
        uses: jupyter-server/jupyter_releaser/.github/actions/finalize-release@v2
        with:
          token: ${{ steps.app-token.outputs.token }}
          release_url: ${{ steps.populate-release.outputs.release_url }}

      - name: "** Next Step **"
        if: ${{ success() }}
        run: |
          echo "Verify the final release"
          echo ${{ steps.finalize-release.outputs.release_url }}

      - name: "** Failure Message **"
        if: ${{ failure() }}
        run: |
          echo "Failed to Publish the Draft Release Url:"
          echo ${{ steps.populate-release.outputs.release_url }}


Publish Changelog

name: "Publish Changelog"
on:
  release:
    types: [published]

  workflow_dispatch:
    inputs:
      branch:
        description: "The branch to target"
        required: false

jobs:
  publish_changelog:
    runs-on: ubuntu-latest
    environment: release
    steps:
      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1

      - uses: actions/create-github-app-token@v1
        id: app-token
        with:
          app-id: ${{ vars.APP_ID }}
          private-key: ${{ secrets.APP_PRIVATE_KEY }}

      - name: Publish changelog
        id: publish-changelog
        uses: jupyter-server/jupyter_releaser/.github/actions/publish-changelog@v2
        with:
          token: ${{ steps.app-token.outputs.token }}
          branch: ${{ github.event.inputs.branch }}

      - name: "** Next Step **"
        run: |
          echo "Merge the changelog update PR: ${{ steps.publish-changelog.outputs.pr_url }}"


Publish and release CumulusCI

name: Publish and release CumulusCI

on:
    push:
        branches:
            - main
        paths:
            - cumulusci/__about__.py

concurrency: publishing

jobs:
    publish-to-pypi:
        name: Publish new release to PyPI
        runs-on: SFDO-Tooling-Ubuntu
        steps:
            - uses: actions/checkout@main
            - name: Set up Python 3.8
              uses: actions/setup-python@v4
              with:
                  python-version: 3.8
                  cache: pip
            - name: Install build tools
              run: python -m pip install hatch tomli tomli-w
            - name: Pin dependencies
              run: python utility/pin_dependencies.py
            - name: Build source tarball and binary wheel
              run: hatch build -c
            - name: Upload to PyPI
              run: hatch publish
              env:
                  HATCH_INDEX_USER: "__token__"
                  HATCH_INDEX_AUTH: ${{ secrets.PYPI_TOKEN }}
            - name: Create release
              env:
                  GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
              run: |
                  VERSION="$(hatch version)"
                  awk '/<!-- latest-start -->/,/<!-- latest-stop -->/' docs/history.md > changelog.md
                  gh release create "v$VERSION" \
                    dist/*.whl \
                    dist/*.tar.gz \
                    --notes-file changelog.md \
                    --title $VERSION


Feature Test

name: Feature Test

on:
    workflow_dispatch:
    pull_request:
        types: [opened, synchronize, reopened] # Default
    push:
        branches:
            - main

jobs:
    lint:
        name: Lint
        if: ${{ contains(fromJSON('["workflow_dispatch", "pull_request"]'), github.event_name) }}
        uses: SFDO-Tooling/.github/.github/workflows/pre-commit.yml@main
    docs:
        name: Build Docs
        if: ${{ github.event_name == 'pull_request' }}
        runs-on: SFDO-Tooling-Ubuntu
        steps:
            - name: "Checkout"
              uses: actions/checkout@v2
              with:
                  fetch-depth: 1
            - name: Set up Python 3.8
              id: py
              uses: actions/setup-python@v4
              with:
                  python-version: 3.8
                  cache: pip
                  cache-dependency-path: "requirements/*.txt"
            - name: Install dependencies
              run: pip install -r requirements_dev.txt
            - name: Build Docs
              run: make docs

    unit_tests:
        name: "Unit tests: ${{ matrix.os }}-${{ matrix.python-version }}"
        runs-on: ${{ matrix.os }}
        strategy:
            fail-fast: false
            matrix:
                os: [macos-latest, SFDO-Tooling-Ubuntu, SFDO-Tooling-Windows]
                python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
                exclude:
                    - os: macos-latest
                      python-version: 3.8
                include:
                    - os: macos-13
                      python-version: 3.8
        steps:
            - uses: actions/checkout@v2
            - name: Set up Python
              uses: actions/setup-python@v4
              with:
                  python-version: "${{ matrix.python-version }}"
                  cache: pip
                  cache-dependency-path: "requirements/*.txt"
            - name: Install dependencies
              run: pip install -r requirements_dev.txt
            - name: Run Pytest
              run: pytest --cov-report= --cov=cumulusci

    robot_api:
        name: "Robot: No browser"
        runs-on: SFDO-Tooling-Ubuntu
        steps:
            - uses: actions/checkout@v2
            - name: Set up Python 3.8
              uses: actions/setup-python@v4
              with:
                  python-version: 3.8
                  cache: pip
                  cache-dependency-path: "requirements/*.txt"
            - name: Install Python dependencies
              run: pip install -r requirements_dev.txt
            - name: Install sfdx
              run: |
                  mkdir sfdx
                  wget -qO- https://developer.salesforce.com/media/salesforce-cli/sf/channels/stable/sf-linux-x64.tar.xz | tar xJ -C sfdx --strip-components 1
                  echo $(realpath sfdx/bin) >> $GITHUB_PATH
            - name: Authenticate Dev Hub
              run: |
                  sfdx plugins --core
                  echo $SFDX_HUB_KEY_BASE64 | base64 --decode > sfdx.key
                  sfdx auth:jwt:grant --clientid $SFDX_CLIENT_ID --jwtkeyfile sfdx.key --username $SFDX_HUB_USERNAME --setdefaultdevhubusername -a hub
              env:
                  SFDX_HUB_KEY_BASE64: ${{ secrets.SFDX_HUB_KEY_BASE64 }}
                  SFDX_CLIENT_ID: ${{ secrets.SFDX_CLIENT_ID }}
                  SFDX_HUB_USERNAME: ${{ secrets.SFDX_HUB_USERNAME }}
            - name: Run robot tests
              run: |
                  coverage run --append $(which cci) task run robot \
                    --org dev \
                    -o name "CumulusCI" \
                    -o suites cumulusci/robotframework/tests \
                    -o include no-browser
            - name: Delete scratch org
              if: always()
              run: |
                  cci org scratch_delete dev
            - name: Store robot results
              if: failure()
              uses: actions/upload-artifact@v1
              with:
                  name: robot
                  path: robot/CumulusCI/results


Release Test

name: Release Test

on:
    pull_request:
        types: [opened, synchronize, reopened] # Default
    workflow_call:
        secrets:
            CUMULUSCI_ORG_packaging:
                required: true
            CUMULUSCI_SERVICE_github:
                required: true
            CCITEST_APP_KEY:
                required: true
            SFDX_CLIENT_ID:
                required: true
            SFDX_HUB_KEY:
                required: true
            SFDX_HUB_KEY_BASE64:
                required: true
            SFDX_HUB_USERNAME:
                required: true

env:
    CUMULUSCI_ORG_scratch: '{"config_file": "orgs/dev.json", "scratch": true}'
    CUMULUSCI_ORG_packaging: ${{ secrets.CUMULUSCI_ORG_packaging }}
    CUMULUSCI_SERVICE_github: ${{ secrets.CUMULUSCI_SERVICE_github }}
    GITHUB_APP_ID: 129383
    GITHUB_APP_KEY: ${{ secrets.CCITEST_APP_KEY }}
    SFDX_CLIENT_ID: ${{ secrets.SFDX_CLIENT_ID }}
    SFDX_HUB_KEY: ${{ secrets.SFDX_HUB_KEY }}
    SFDX_HUB_KEY_BASE64: ${{ secrets.SFDX_HUB_KEY_BASE64 }}
    SFDX_HUB_USERNAME: ${{ secrets.SFDX_HUB_USERNAME }}

jobs:
    test_artifacts:
        name: "Test Package Artifacts"
        runs-on: SFDO-Tooling-Ubuntu
        steps:
            - uses: actions/checkout@v3
            - name: Set up Python 3.8
              uses: actions/setup-python@v4
              with:
                  python-version: 3.8
                  cache: pip
                  cache-dependency-path: "requirements/*.txt"
            - name: Install build tools
              run: pip install hatch
            - name: Test source tarball and binary wheel
              run: |
                  hatch build
            - name: Test install of wheel
              run: |
                  pip install dist/cumulusci*.whl
                  pip show cumulusci
                  pip uninstall -y cumulusci
            - name: Test install of sdist
              run: |
                  pip install dist/cumulusci*.tar.gz
                  pip show cumulusci
                  pip uninstall -y cumulusci
            - name: Store artifacts
              if: failure()
              uses: actions/upload-artifact@v3
              with:
                  name: packages
                  path: dist

    test_release:
        name: "Test Release Flows"
        runs-on: SFDO-Tooling-Ubuntu
        concurrency: release
        steps:
            - uses: actions/checkout@v3
            - name: Set up Python 3.8
              uses: actions/setup-python@v4
              with:
                  python-version: 3.8
                  cache: pip
                  cache-dependency-path: "requirements/*.txt"
            - name: Install Python dependencies
              run: pip install -r requirements_dev.txt
            - name: Install sfdx
              run: |
                  mkdir sfdx
                  wget -qO- https://developer.salesforce.com/media/salesforce-cli/sfdx/channels/stable/sfdx-linux-x64.tar.xz | tar xJ -C sfdx --strip-components 1
                  echo $(realpath sfdx/bin) >> $GITHUB_PATH
            - name: Authenticate Dev Hub
              run: |
                  sfdx plugins --core
                  echo $SFDX_HUB_KEY_BASE64 | base64 --decode > sfdx.key
                  sfdx auth:jwt:grant --clientid $SFDX_CLIENT_ID --jwtkeyfile sfdx.key --username $SFDX_HUB_USERNAME --setdefaultdevhubusername -a hub
            - name: Check out CumulusCI-Test
              run: |
                  git clone https://github.com/SFDO-Tooling/CumulusCI-Test
            - name: Run ci_feature flow
              run: |
                  cd CumulusCI-Test
                  coverage run --append --rcfile=../pyproject.toml --source=../cumulusci $(which cci) flow run ci_feature --org scratch --delete-org
            - name: Run ci_beta flow
              run: |
                  cd CumulusCI-Test
                  coverage run --append --rcfile=../pyproject.toml --source=../cumulusci $(which cci) flow run ci_beta --org scratch --delete-org
            - name: Run ci_master flow
              run: |
                  cd CumulusCI-Test
                  coverage run --append --rcfile=../pyproject.toml --source=../cumulusci $(which cci) flow run ci_master --org scratch --delete-org
            - name: Run release_beta flow
              run: |
                  export SFDX_HUB_KEY="$(echo $SFDX_HUB_KEY_BASE64 | base64 --decode)"
                  cd CumulusCI-Test
                  coverage run --append --rcfile=../pyproject.toml --source=../cumulusci $(which cci) flow run release_beta --org packaging


Integration Tests

name: Integration Tests

on:
    pull_request_review:
        types: [submitted]

    workflow_call:
        secrets:
            CUMULUSCI_SERVICE_github:
                required: true
            CCITEST_APP_KEY:
                required: true

    workflow_dispatch:

env:
    CUMULUSCI_KEY: ${{ secrets.CUMULUSCI_KEY }}
    CUMULUSCI_SERVICE_github: ${{ secrets.CUMULUSCI_SERVICE_github }}
    GITHUB_APP_ID: 129383
    GITHUB_APP_KEY: ${{ secrets.CCITEST_APP_KEY }}

jobs:
    org_backed_tests:
        name: "Org-connected Tests"
        runs-on: SFDO-Tooling-Ubuntu
        steps:
            - uses: actions/checkout@v2
            - name: Set up Python 3.8
              uses: actions/setup-python@v4
              with:
                  python-version: 3.8
                  cache: pip
                  cache-dependency-path: "requirements/*.txt"
            - name: Install Python dependencies
              run: |
                  python -m pip install -U pip
                  pip install -r requirements_dev.txt
            - name: Install sfdx
              run: |
                  mkdir sfdx
                  wget -qO- https://developer.salesforce.com/media/salesforce-cli/sfdx/channels/stable/sfdx-linux-x64.tar.xz | tar xJ -C sfdx --strip-components 1
                  echo $(realpath sfdx/bin) >> $GITHUB_PATH
            - name: Authenticate Dev Hub
              run: |
                  sfdx plugins --core
                  echo $SFDX_HUB_KEY_BASE64 | base64 --decode > sfdx.key
                  sfdx auth:jwt:grant --clientid $SFDX_CLIENT_ID --jwtkeyfile sfdx.key --username $SFDX_HUB_USERNAME --setdefaultdevhubusername -a hub
              env:
                  SFDX_HUB_KEY_BASE64: ${{ secrets.SFDX_HUB_KEY_BASE64 }}
                  SFDX_CLIENT_ID: ${{ secrets.SFDX_CLIENT_ID }}
                  SFDX_HUB_USERNAME: ${{ secrets.SFDX_HUB_USERNAME }}
            - name: Re-make VCR tapes (transiently)
              run: make vcr
            - name: Delete scratch org
              if: always()
              run: |
                  cci org scratch_delete pytest
    robot_ui:
        name: "Robot: ${{ matrix.job-name }}"
        runs-on: SFDO-Tooling-Ubuntu
        strategy:
            fail-fast: false
            matrix:
                include:
                    - browser: "BROWSER:headlesschrome"
                      job-name: "Chrome"
                      org-shape: "dev"
                    # - browser: "BROWSER:headlessfirefox"
                    #   job-name: "Firefox"
                    #   org-shape: "dev"
                    # - browser: "BROWSER:headlesschrome"
                    #   job-name: "Pre-release"
                    #   org-shape: "prerelease"
        steps:
            - uses: actions/checkout@v2
            - name: Set up Python 3.8
              uses: actions/setup-python@v4
              with:
                  python-version: 3.8
                  cache: pip
                  cache-dependency-path: "requirements/*.txt"
            - name: Install Python dependencies
              run: pip install -r requirements_dev.txt
            - name: Install sfdx
              run: |
                  mkdir sfdx
                  wget -qO- https://developer.salesforce.com/media/salesforce-cli/sfdx/channels/stable/sfdx-linux-x64.tar.xz | tar xJ -C sfdx --strip-components 1
                  echo $(realpath sfdx/bin) >> $GITHUB_PATH
            - name: Initialize Browser/Playwright
              run: cci robot install_playwright
            - name: Authenticate Dev Hub
              run: |
                  sfdx plugins --core
                  echo $SFDX_HUB_KEY_BASE64 | base64 --decode > sfdx.key
                  sfdx auth:jwt:grant --clientid $SFDX_CLIENT_ID --jwtkeyfile sfdx.key --username $SFDX_HUB_USERNAME --setdefaultdevhubusername -a hub
              env:
                  SFDX_HUB_KEY_BASE64: ${{ secrets.SFDX_HUB_KEY_BASE64 }}
                  SFDX_CLIENT_ID: ${{ secrets.SFDX_CLIENT_ID }}
                  SFDX_HUB_USERNAME: ${{ secrets.SFDX_HUB_USERNAME }}
            - name: Run robot tests
              run: |
                  coverage run --append $(which cci) task run robot \
                    --org ${{ matrix.org-shape }} \
                    -o suites cumulusci/robotframework/tests/salesforce \
                    -o exclude no-browser \
                    -o vars ${{ matrix.browser }}
            - name: Delete scratch org
              if: always()
              run: |
                  cci org scratch_delete ${{ matrix.org-shape }}
            - name: Store robot results
              if: failure()
              uses: actions/upload-artifact@v1
              with:
                  name: robot
                  path: robot/CumulusCI/results


Scheduled Chores

name: Scheduled Chores

on:
    workflow_dispatch:
    schedule:
        - cron: "0 0 * * 0" # At 00:00 on Sunday

jobs:
    check_api_versions:
        runs-on: SFDO-Tooling-Ubuntu
        outputs:
            hub_version: ${{ steps.devhub-api-version.outputs.hub_version }}
            cci_version: ${{ steps.cci-api-version.outputs.cci_version }}
        steps:
            - uses: actions/checkout@v3
              with:
                  ref: main
            - name: Set up Python
              uses: actions/setup-python@v4
            - name: Get Dev Hub API Version
              id: devhub-api-version
              env:
                  HUB_URL: ${{ format('{0}/services/data', secrets.SFDO_HUB_URL) }}
              run: |
                  version=$(curl -s $HUB_URL | jq -r '.[-1] | .version')
                  echo "::set-output name=hub_version::$version"
            - name: Get CURRENT_SF_API_VERSION
              id: cci-api-version
              run: |
                  version=$(yq '.project.package.api_version' cumulusci/cumulusci.yml)
                  echo "::set-output name=cci_version::$version"
    update_api_versions:
        runs-on: SFDO-Tooling-Ubuntu
        needs: check_api_versions
        if: ${{ needs.check_api_versions.outputs.hub_version != needs.check_api_versions.outputs.cci_version }}
        env:
            VERSION: ${{ needs.check_api_versions.outputs.hub_version }}
        steps:
            - uses: actions/checkout@v3
              with:
                  fetch-depth: 0
                  ref: main
            - name: Set CURRENT_SF_API_VERSION
              id: set-cci-api-version
              run: |
                  yq --indent 4 --inplace '
                    .project.package.api_version = strenv(VERSION)
                  ' cumulusci/cumulusci.yml
            - name: Commit changes
              run: |
                  git config user.name github-actions[bot]
                  git config user.email 41898282+github-actions[bot]@users.noreply.github.com
                  git switch -c "update-sfdc-api-v$VERSION"
                  git add cumulusci/cumulusci.yml
                  git commit -m "Automated update to sfdc API version $VERSION"
                  git push origin "update-sfdc-api-v$VERSION"
            - env:
                  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
              run: |
                  gh pr create --fill --label 'auto-pr'


Update Python Dependencies

name: Update Python Dependencies

on:
    workflow_dispatch:

jobs:
    update_python_dependencies:
        uses: SFDO-Tooling/.github/.github/workflows/update_python_dependencies.yml@main
        with:
            python-version: 3.8


Draft release pull request

name: Draft release pull request

on:
    workflow_dispatch:
        inputs:
            version:
                description: "Select version bump"
                default: "minor"
                type: choice
                options:
                    - major
                    - minor
                    - patch
                    - alpha
                    - beta
                    - preview
                    - dev

jobs:
    generate-changelog:
        name: Create a PR to update version and release notes
        runs-on: SFDO-Tooling-Ubuntu
        steps:
            - uses: actions/checkout@main
            - name: Set up Python 3.8
              uses: actions/setup-python@v4
              with:
                  python-version: 3.8
                  cache: pip
            - name: Install build tool
              run: python -m pip install hatch
            - name: Bump version
              run: hatch version $VERSION
              env:
                  VERSION: ${{ inputs.version }}
            - name: Generate release notes
              id: changelog
              env:
                  GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
              run: |
                  PREVIOUS_VERSION=$(gh release view --json tagName --jq .tagName)
                  NEXT_VERSION="v$(hatch version)"
                  echo "## $NEXT_VERSION ($(date -I))" > changelog.md
                  gh api \
                    --method POST \
                    -H "Accept: application/vnd.github.v3+json" \
                    /repos/SFDO-Tooling/CumulusCI/releases/generate-notes \
                    -f previous_tag_name=$PREVIOUS_VERSION \
                    -f target_commitish='main' \
                    -f tag_name=$NEXT_VERSION \
                    --jq '.body' |
                    sed -e 's_\(https.*\/\)\([0-9]*\)$_[#\2](\1\2)_' \
                        -e 's_by @\(.*\) in_by [@\1](https://github.com/\1) in_' >> changelog.md
                  python utility/update-history.py
            - name: Lint history
              run: |
                  npm install prettier
                  npx prettier --write docs/history.md
            - name: Commit changes
              run: |
                  git config user.name github-actions[bot]
                  git config user.email 41898282+github-actions[bot]@users.noreply.github.com
                  git switch -c "release-$(hatch version)"
                  git add docs/history.md cumulusci/__about__.py
                  git commit -m "Update changelog (automated)"
                  git push origin "release-$(hatch version)"
            - name: Commit changes and open PR
              env:
                  GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
              run: |
                  gh pr create --title "Release v$(hatch version)" --fill --label 'auto-pr'


ci

---
name: ci

# yamllint disable-line rule:truthy
on:
  push:
  pull_request:

concurrency:
  group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}}
  cancel-in-progress: true

jobs:
  nix:
    uses: ./.github/workflows/nix.yml
  unit-tests:
    uses: ./.github/workflows/test_and_package.yml
  docs:
    uses: ./.github/workflows/docs.yml
    secrets: inherit
  publish:
    needs: [nix, unit-tests, docs]
    uses: ./.github/workflows/publish.yml
  all:
    needs: [unit-tests, publish]
    runs-on: ubuntu-22.04
    steps:
      - name: Success
        run: "true"


docs push

---
# yamllint disable rule:line-length
name: docs push

# yamllint disable-line rule:truthy
on:
  workflow_dispatch:
  workflow_call:

concurrency:
  group: docs-${{github.ref}}-${{github.event.pull_request.number || github.run_number}}
  cancel-in-progress: true

jobs:
  build-and-push-docs:
    runs-on: ubuntu-22.04
    strategy:
      matrix:
        container:
          - 'shivupa/cclib-ci:py311-edge'
    container:
      image: ${{ matrix.container }}
    defaults:
      run:
        shell: bash -eo pipefail -l {0}
    steps:
      - name: Installs for ssh-agent
        run: |
          apt-get update && apt-get install -y openssh-client
      - name: Checkout
        uses: actions/checkout@v4
        with:
          # so versioningit can figure out the version
          fetch-depth: 0
          fetch-tags: true
      - name: Allow repo access (see https://github.com/actions/checkout/issues/760)
        run: |
          git config --global --add safe.directory /__w/cclib/cclib
      - name: Prepare conda environment
        run: |
          echo "/opt/conda/envs/cclib/bin" >> $GITHUB_PATH
      - name: Build docs
        run: cd doc && make default
      - name: Make a no jekyll file
        run: touch doc/sphinx/_build/html/.nojekyll
      - name: Pushes to another repository
        id: push_directory
        uses: cpina/github-action-push-to-another-repository@main
        env:
          SSH_DEPLOY_KEY: ${{ secrets.CCLIB_DOCS_KEY }}
        with:
          source-directory: doc/sphinx/_build/html
          destination-github-username: 'cclib'
          destination-repository-name: 'cclib.github.io'
          commit-message: See ORIGIN_COMMIT from $GITHUB_REF
          target-branch: master
        if: github.ref == 'refs/heads/master'


build and test nix package

---
name: build and test nix package

# yamllint disable-line rule:truthy
on:
  workflow_dispatch:
  workflow_call:

concurrency:
  group: nix-${{github.ref}}-${{github.event.pull_request.number || github.run_number}}
  cancel-in-progress: true

jobs:
  nix-build:
    runs-on: ubuntu-latest
    steps:
      # Install Nix on the runner
      - uses: cachix/install-nix-action@V27
        with:
          nix_path: nixpkgs=channel:nixos-unstable
      # Pull from the cachix cache
      - uses: cachix/cachix-action@v15
        with:
          name: nix-qchem
      # Checkout of the current head in the working dir
      - uses: actions/checkout@v4
      # Build the nix package
      - name: Build nix package
        run: nix flake check -L


publish packages to package indices

---
# yamllint disable rule:line-length
name: publish packages to package indices

# yamllint disable-line rule:truthy
on:
  workflow_dispatch:
  workflow_call:

concurrency:
  group: publish-${{github.ref}}-${{github.event.pull_request.number || github.run_number}}
  cancel-in-progress: true

env:
  # For now always take the "reduced" form of the version, even for those
  # actions runs that do not push to a package index.
  VERSIONINGIT_FOR_PACKAGE_INDEX: true

jobs:

  # Taken from
  # https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/,
  # where building the distribution happens every commit after running the
  # tests

  publish-to-testpypi:
    name: Publish Python distribution to TestPyPI
    runs-on: ubuntu-22.04

    # publish on merge to default branch or any tag
    if: github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/')

    environment:
      name: testpypi
      url: https://test.pypi.org/p/cclib

    permissions:
      id-token: write  # IMPORTANT: mandatory for trusted publishing

    steps:
      - name: Download all the dists
        uses: actions/download-artifact@v4
        with:
          name: python-package-distributions
          path: dist/
      - name: Publish distribution to TestPyPI
        uses: pypa/gh-action-pypi-publish@release/v1
        with:
          repository-url: https://test.pypi.org/legacy/

  publish-to-pypi:
    name: Publish Python distribution to PyPI
    runs-on: ubuntu-22.04

    # only publish on release/versioned tag pushes
    if: startsWith(github.ref, 'refs/tags/v')

    environment:
      name: pypi
      url: https://pypi.org/p/cclib

    permissions:
      id-token: write  # IMPORTANT: mandatory for trusted publishing

    steps:
      - name: Download all the dists
        uses: actions/download-artifact@v4
        with:
          name: python-package-distributions
          path: dist/
      - name: Publish distribution to PyPI
        uses: pypa/gh-action-pypi-publish@release/v1

  github-release:
    name: >-
      Sign the Python distribution with Sigstore
      and upload them to GitHub Release
    runs-on: ubuntu-22.04

    # make (draft) release on any tag
    if: startsWith(github.ref, 'refs/tags/')

    permissions:
      contents: write  # IMPORTANT: mandatory for making GitHub Releases
      id-token: write  # IMPORTANT: mandatory for sigstore

    steps:
      - name: Download all the dists
        uses: actions/download-artifact@v4
        with:
          name: python-package-distributions
          path: dist/
      - name: Sign the dists with Sigstore
        uses: sigstore/gh-action-sigstore-python@v2.1.1
        with:
          inputs: >-
            ./dist/*.tar.gz
            ./dist/*.whl
      - name: Create GitHub Release (draft)
        env:
          GITHUB_TOKEN: ${{ github.token }}
        run: >-
          gh release create
          '${{ github.ref_name }}'
          --repo '${{ github.repository }}'
          --draft
          --notes ""
      - name: Upload artifact signatures to GitHub Release
        env:
          GITHUB_TOKEN: ${{ github.token }}
        # Upload to GitHub Release using the `gh` CLI.
        # `dist/` contains the built packages, and the
        # sigstore-produced signatures and certificates.
        run: >-
          gh release upload
          '${{ github.ref_name }}' dist/**
          --repo '${{ github.repository }}'


---
# yamllint disable rule:line-length
name: run tests and create installable packages

# yamllint disable-line rule:truthy
on:
  workflow_dispatch:
  workflow_call:

concurrency:
  group: test-${{github.ref}}-${{github.event.pull_request.number || github.run_number}}
  cancel-in-progress: true

# For now always take the "reduced" form of the version, even for those
# actions runs that do not push to a package index.
env:
  VERSIONINGIT_FOR_PACKAGE_INDEX: true

jobs:

  run-tests:
    runs-on: ubuntu-22.04
    strategy:
      matrix:
        container:
          - 'shivupa/cclib-ci:py38-edge'
          - 'shivupa/cclib-ci:py39-edge'
          - 'shivupa/cclib-ci:py310-edge'
          - 'shivupa/cclib-ci:py311-edge'
    container:
      image: ${{ matrix.container }}
    defaults:
      run:
        shell: bash -eo pipefail -l {0}
    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          # so versioningit can figure out the version
          fetch-depth: 0
          fetch-tags: true
      - name: Allow repo access (see https://github.com/actions/checkout/issues/760)
        run: |
          git config --global --add safe.directory /__w/cclib/cclib
      - name: Prepare conda environment
        run: |
          echo "/opt/conda/envs/cclib/bin" >> "${GITHUB_PATH}"
      - name: Install cclib
        run: |
          python -m pip install .
      - name: Filter code
        uses: dorny/paths-filter@v3
        id: filter
        with:
          filters: |
            methods:
              - 'cclib/cclib/method/**'
              - 'test/method/**'
      - name: Test core code with pytest
        run: |
          env | sort
          bash .github/scripts/run_pytest.bash 2
      - name: Test methods with pytest
        if: steps.filter.outputs.methods == 'true'
        run: |
          bash .github/scripts/run_pytest_methods.bash 2
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v4
        with:
          files: ./coverage-unit.xml,./coverage-regression.xml,./coverage-method.xml
          name: codecov-cclib
          fail_ci_if_error: false
          verbose: false
        env:
          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
        if: matrix.container == 'shivupa/cclib-ci:py311-edge'
      - name: Install pypa/build
        run: |
          python -m pip install build
      - name: Build distribution packages (binary wheel and source tarball)
        run: |
          python -m build
      - name: Check packages with twine
        run: |
          python -m pip install twine
          python -m twine check dist/*
      - name: Store the distribution packages
        uses: actions/upload-artifact@v4
        with:
          name: python-package-distributions
          path: dist/
        if: matrix.container == 'shivupa/cclib-ci:py311-edge'

  # We check the installation using all Python versions using the packages
  # created from a single version, since the packages are platform and version
  # independent.
  check-installation:
    needs:
      - run-tests
    runs-on: ubuntu-22.04
    strategy:
      matrix:
        install-method: [wheel, source]
        python-version:
          - 3.7
          - 3.8
          - 3.9
          - "3.10"
          - 3.11
          - 3.12
    steps:
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - name: Download the distribution packages
        uses: actions/download-artifact@v4
        with:
          name: python-package-distributions
          path: dist/
      - name: Update environment
        run: |
          python -m pip install -U setuptools pip
      - name: Install from wheel
        run: |
          python -m pip install dist/*.whl
        if: matrix.install-method == 'wheel'
      - name: Install from source
        run: |
          python -m pip install dist/*.tar.gz
        if: matrix.install-method == 'source'
      - name: Check cclib install
        run: |
          cd ~
          python -c 'import cclib; print(cclib.__version__)'
          python -c 'from cclib.io import ccread; data = ccread("https://raw.githubusercontent.com/cclib/cclib-data/master/Gaussian/Gaussian16/water.log"); print(data.zpve)'


Continuous Integration

name: Continuous Integration

on:
  push:
    branches:
      - main
    tags:
      - "*"
  pull_request:

env:
  IS_TAG_BUILD: ${{ startsWith(github.event.ref, 'refs/tags') }}

# SECRETS
# - PYPI_TOKEN: publishing token for amn41 account, needs to be maintainer of
#               RasaHQ/rasa-sdk on pypi (account credentials in 1password)

jobs:
  quality:
    name: Code Quality
    runs-on: ubuntu-22.04

    steps:
      - name: Checkout git repository 🕝
        uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3

      - name: Set up Python 3.10 🐍
        uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435
        with:
          python-version: '3.10'

      - name: Read Poetry Version 🔢
        run: |
          echo "POETRY_VERSION=$(scripts/poetry-version.sh)" >> $GITHUB_ENV
        shell: bash

      - name: Install poetry 🦄
        uses: Gr1N/setup-poetry@15821dc8a61bc630db542ae4baf6a7c19a994844
        with:
          poetry-version: ${{ env.POETRY_VERSION }}

      - name: Load Poetry Cached Libraries ⬇
        uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8
        with:
          path: ~/.cache/pypoetry/virtualenvs
          key: ${{ runner.os }}-poetry-3.10-${{ hashFiles('**/poetry.lock') }}
          restore-keys: ${{ runner.os }}-poetry-

      - name: Checkout target branch to be able to diff
        if: github.event_name == 'pull_request'
        run: |
          git fetch origin "${{ github.base_ref }}"
          echo "DOCSTRING_DIFF_BRANCH=origin/${{ github.base_ref }}" >> $GITHUB_ENV

          # Fetch entire history for current branch so that `make lint-docstrings`
          # can calculate the proper diff between the branches
          git fetch --unshallow origin "${{ github.ref }}"

      - name: Install Dependencies 📦
        run: make install

      - name: Listing Dependencies 📦
        run: poetry show

      - name: Lint Code 🎎
        run: |
          # If it's not a pull request, $DOCSTRING_DIFF_BRANCH is unset.
          # This will result in an empty diff, which effictively means that
          # make lint-docstrings will be skipped for other events than `pull_request`
          make lint BRANCH=$DOCSTRING_DIFF_BRANCH

      - name: Check Types 📚
        run: make types

  test:
    name: Run Tests
    runs-on: ubuntu-22.04
    strategy:
      matrix:
        python-version: [3.8, 3.9, '3.10']

    steps:
      - name: Checkout git repository 🕝
        uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3

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

      - name: Read Poetry Version 🔢
        run: |
          echo "POETRY_VERSION=$(scripts/poetry-version.sh)" >> $GITHUB_ENV
        shell: bash

      - name: Install poetry 🦄
        uses: Gr1N/setup-poetry@15821dc8a61bc630db542ae4baf6a7c19a994844
        with:
          poetry-version: ${{ env.POETRY_VERSION }}

      - name: Load Poetry Cached Libraries ⬇
        uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8
        with:
          path: ~/.cache/pypoetry/virtualenvs
          key: ${{ runner.os }}-poetry-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}
          restore-keys: ${{ runner.os }}-poetry-

      - name: Install Dependencies 📦
        run: make install

      - name: Test Code 🔍
        run: make test

      - name: Send Coverage Report 📊
        if: matrix.python-version == '3.10'
        env:
          COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          COVERALLS_SERVICE_NAME: github
        run: poetry run coveralls

  docker_linter:
    name: Lint Dockerfile
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
      - name: Lint Dockerfile
        uses: brpaz/hadolint-action@1623ba61710b974b55ba455930e6f2c8ef919778
        with:
          dockerfile: "Dockerfile"

  build_docker_image_set_env:
    name: Prepare environment for Docker build
    runs-on: ubuntu-22.04
    outputs:
      # Tag name used for intermediate images created during Docker image builds, e.g. 3886 - a PR number
      image_tag: ${{ steps.set_output.outputs.image_tag }}
      # Return 'true' if tag version is equal or higher than the latest tagged rasa-sdk version
      is_newest_version: ${{ steps.rasa_sdk_get_version.outputs.is_newest_version }}
    steps:
      # Due to an issue with checking out a wrong commit, we make sure
      # to checkout HEAD commit for a pull request.
      # More details: https://github.com/actions/checkout/issues/299
      - name: Checkout pull request HEAD commit instead of merge commit 🕝
        uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
        if: github.event_name == 'pull_request'
        with:
          ref: ${{ github.event.pull_request.head.sha }}

      - name: Checkout git repository 🕝
        uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
        if: github.event_name != 'pull_request'

      # Set environment variables for a pull request
      #
      # In this scenario, we've created a PR #1234
      #
      # Example output:
      # IMAGE_TAG=1234
      - name: Set environment variables - pull_request
        if: github.event_name == 'pull_request' && env.IS_TAG_BUILD == 'false'
        run: |
          echo "IMAGE_TAG=${{ github.event.number }}" >> $GITHUB_ENV

      # Set environment variables for a tag
      #
      # In this scenario, we've pushed the '2.4.1' tag
      #
      # Example output:
      # IMAGE_TAG=2.4.1
      - name: Set environment variables - push - tag
        if: github.event_name == 'push' && env.IS_TAG_BUILD == 'true'
        run: |
          TAG_NAME=${GITHUB_REF#refs/tags/}
          echo "IMAGE_TAG=${TAG_NAME}" >> $GITHUB_ENV

      # Set environment variables for a branch
      #
      # In this scenario, we've pushed changes into the main branch
      #
      # Example output:
      # IMAGE_TAG=main
      - name: Set environment variables - push - branch
        if: github.event_name == 'push' && env.IS_TAG_BUILD == 'false'
        run: |
          BRANCH_NAME=${GITHUB_REF#refs/heads/}
          echo "IMAGE_TAG=${BRANCH_NAME}" >> $GITHUB_ENV

      - name: Set output
        id: set_output
        run: |
          echo "image_tag=${{ env.IMAGE_TAG }}" >> $GITHUB_OUTPUT

      - name: Check if tag version is equal or higher than the latest tagged rasa-sdk version
        id: rasa_sdk_get_version
        if: env.IS_TAG_BUILD == 'true'
        run: |
          # Get latest tagged rasa-sdk version
          git fetch --depth=1 origin "+refs/tags/*:refs/tags/*"
          # Fetch branch history
          git fetch --prune --unshallow
          LATEST_TAGGED_NON_ALPHA_RASA_SDK_VERSION=$(git tag | sort -r -V | grep -E "^[0-9.]+$" | head -n1)
          CURRENT_TAG=${GITHUB_REF#refs/tags/}
          # Return 'true' if tag version is equal or higher than the latest tagged rasa-sdk version
          IS_NEWEST_VERSION=$((printf '%s\n%s\n' "${LATEST_TAGGED_NON_ALPHA_RASA_SDK_VERSION}" "$CURRENT_TAG" \
            | sort -V -C && echo true || echo false) || true)

          if [[ "${IS_NEWEST_VERSION}" == "true" && "$CURRENT_TAG" =~ ^[0-9.]+$ ]]; then
            echo "is_newest_version=true" >> $GITHUB_OUTPUT
          else
            echo "is_newest_version=false" >> $GITHUB_OUTPUT
          fi

  build_docker_image:
    name: Build Docker image
    runs-on: ubuntu-22.04
    needs: [quality, test, docker_linter, build_docker_image_set_env]

    steps:
      # Due to an issue with checking out a wrong commit, we make sure
      # to checkout HEAD commit for a pull request.
      # More details: https://github.com/actions/checkout/issues/299
      - name: Checkout pull request HEAD commit instead of merge commit 🕝
        uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
        if: github.event_name == 'pull_request'
        with:
          ref: ${{ github.event.pull_request.head.sha }}

      - name: Checkout git repository 🕝
        uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
        if: github.event_name != 'pull_request'

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@4b4e9c3e2d4531116a6f8ba8e71fc6e2cb6e6c8c
        with:
          version: v0.5.1
          driver: docker

      - name: Set environment variables
        run: |
          echo "IMAGE_TAG=${{ needs.build_docker_image_set_env.outputs.image_tag }}" >> $GITHUB_ENV

      - name: Build Docker image
        run: |
          docker buildx bake --load

      - name: Login to Docker Hub Registry 🔢
        if: github.event_name == 'push' && env.IS_TAG_BUILD == 'true' && github.repository == 'RasaHQ/rasa-sdk'
        run: echo ${{ secrets.DOCKER_HUB_PASSWORD }} | docker login -u ${{ secrets.DOCKER_HUB_USERNAME }} --password-stdin || true

      - name: Push Docker image 📦
        if: github.event_name == 'push' && env.IS_TAG_BUILD == 'true' && github.repository == 'RasaHQ/rasa-sdk'
        run: |
          IS_NEWEST_VERSION=${{ needs.build_docker_image_set_env.outputs.is_newest_version }}

          # Push image
          docker push rasa/rasa-sdk:${IMAGE_TAG}

          # Tag the image as latest
          if [[ "${IS_NEWEST_VERSION}" == "true" ]]; then
            docker tag rasa/rasa-sdk:${IMAGE_TAG} rasa/rasa-sdk:latest
            docker push rasa/rasa-sdk:latest
          fi

  deploy:
    name: Deploy to PyPI
    runs-on: ubuntu-22.04

    # deploy will only be run when there is a tag available
    if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags')
    needs: [quality, test, build_docker_image] # only run after all other stages succeeded

    steps:
      - name: Checkout git repository 🕝
        uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3

      - name: Set up Python 3.10 🐍
        uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435
        with:
          python-version: '3.10'

      - name: Read Poetry Version 🔢
        run: |
          echo "POETRY_VERSION=$(scripts/poetry-version.sh)" >> $GITHUB_ENV
        shell: bash

      - name: Install poetry 🦄
        uses: Gr1N/setup-poetry@15821dc8a61bc630db542ae4baf6a7c19a994844
        with:
          poetry-version: ${{ env.POETRY_VERSION }}

      - name: Build ⚒ Distributions
        run: poetry build

      - name: Publish to PyPI 📦
        uses: pypa/gh-action-pypi-publish@bea5cda687c2b79989126d589ef4411bedce0195
        with:
          user: __token__
          password: ${{ secrets.PYPI_TOKEN }}
          skip_existing: true

      - name: Publish Release Notes 🗞
        if: env.IS_TAG_BUILD
        env:
          GITHUB_TAG: ${{ github.ref }}
          GITHUB_REPO_SLUG: ${{ github.repository }}
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          GITHUB_TAG=${GITHUB_TAG/refs\/tags\//}
          sudo apt-get update
          sudo apt-get -y install pandoc
          pip install -U github3.py pep440_version_utils
          python3 ${GITHUB_WORKSPACE}/scripts/publish_gh_release_notes.py

      - name: Get RASA SDK Version
        env:
          RASA_SDK_VERSION: ${{ github.ref }}
        run: |
          echo "RASA_SDK_VERSION=${RASA_SDK_VERSION/refs\/tags\//}" >> $GITHUB_ENV

      - name: Notify Slack 💬
        if: env.IS_TAG_BUILD && success()
        env:
          SLACK_WEBHOOK: ${{ secrets.SLACK_RELEASE_ASSISTANT_RELEASE_WEBHOOK }}
        uses: Ilshidur/action-slack@2.1.0
        with:
          args: "⚡ New *Rasa SDK* version ${{ env.RASA_SDK_VERSION }} has been released! Changelog: https://github.com/RasaHQ/rasa-sdk/blob/${{ env.RASA_SDK_VERSION }}/CHANGELOG.mdx"

      - name: Notify Slack of Failure ⛔
        if: env.IS_TAG_BUILD && failure()
        env:
          SLACK_WEBHOOK: ${{ secrets.SLACK_RELEASE_ASSISTANT_DEV_TRIBE_WEBHOOK }}
        uses: Ilshidur/action-slack@2.1.0
        with:
          args: "⛔ *Rasa SDK* version ${{ env.RASA_SDK_VERSION }} could not be released 😱 GitHub Actions: https://github.com/RasaHQ/rasa-sdk/actions?query=branch%3A${{ env.RASA_SDK_VERSION }}"


Backup Rasa SDK repo to S3

name: Backup Rasa SDK repo to S3
on:
  schedule:
    # Run cron job at 7AM Monday to Sunday.
    - cron: "0 7 * * *"
  workflow_dispatch:

permissions:
  id-token: write
  contents: read

jobs:
  backup:
    runs-on: ubuntu-22.04
    steps:
      - name: Checkout repository
        uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
        with:
          fetch-depth: 0

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@8c3f20df09ac63af7b3ae3d7c91f105f857d8497 #v3.0.1
        with:
          role-to-assume: ${{ secrets.GIT_BACKUP_ROLE_ARN }}
          aws-region: eu-central-1

      # Determine the date in the right format for us to tag the image.
      - name: Get current date
        id: date
        run: echo "date=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT

      - name: Create Git bundle
        run: git bundle create --progress rasa-sdk-${{ steps.date.outputs.date }}.bundle --all

      - name: Verify Git bundle
        run: git bundle verify rasa-sdk-${{ steps.date.outputs.date }}.bundle

      - name: Upload bundle to S3
        run: aws s3 cp rasa-sdk-${{ steps.date.outputs.date }}.bundle s3://${{ secrets.GIT_BACKUP_BUCKET }}/rasa-sdk/


Scan Rasa SDK App Dependencies

name: Scan Rasa SDK App Dependencies

on:
  workflow_dispatch:

jobs:
  scan_and_alert:
    runs-on: ubuntu-22.04

    steps:
    - name: Checkout repository to check tags
      uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9

    - name: Get latest tagged release
      id: find-tag
      run: |
        git fetch --depth=1 origin "+refs/tags/*:refs/tags/*"
        echo "tag=$(git tag | sort -r -V | grep -E "^[0-9]+.[0-9]+.[0-9]+$" | head -n1)" >> $GITHUB_OUTPUT

    - name: Scan image for language dependency vulnerabilities
      id: trivy
      uses: aquasecurity/trivy-action@d63413b0a4a4482237085319f7f4a1ce99a8f2ac
      continue-on-error: true
      with:
        exit-code: '1'
        format: 'table'
        severity: 'MEDIUM,HIGH,CRITICAL'
        ignore-unfixed: true
        security-checks: 'vuln'
        image-ref: 'rasa/rasa-sdk:${{ steps.find-tag.outputs.tag }}'
        vuln-type: 'library'
        scan-type: 'image'
        output: scan-result.txt

    - id: auth-security-reports
      name: Authenticate with gcloud for saving report 🎫
      uses: 'google-github-actions/auth@ceee102ec2387dd9e844e01b530ccd4ec87ce955'
      with:
        credentials_json: '${{ secrets.GCP_WRITE_PUBLIC_SECURITY_REPORTS_KEY }}'

    - name: Set up Cloud SDK
      uses: google-github-actions/setup-gcloud@d51b5346f85640ec2aa2fa057354d2b82c2fcbce

    - name: Get current date
      id: date
      run: echo "date=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT

    - name: Upload scan report to GCS
      run: |
        gsutil -h "Content-Type:text/plain; charset=utf-8" -h "Cache-Control:public, max-age=0, no-transform" cp scan-result.txt gs://${{ secrets.GCP_SECURITY_REPORTS_BUCKET }}/rasa-sdk-dependency-report-${{ steps.find-tag.outputs.tag }}-${{ steps.date.outputs.date }}.txt

    - name: Alert Slack on findings
      if: steps.trivy.outcome == 'failure'
      uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
      with:
        channel-id: 'C01M5TAHDHA,C04Q26JSD6U'
        payload: |
          {
            "text": "Dependency vulnerabilities have been found in the Docker image for Rasa SDK ${{ steps.find-tag.outputs.tag }} that could impact customer deployments. Please schedule these for resolution.",
            "blocks": [
              {
                "type": "section",
                "text": {
                  "type": "mrkdwn",
                  "text": "Dependency vulnerabilities have been found in the Docker image for Rasa SDK ${{ steps.find-tag.outputs.tag }} that could impact customer deployments. Please schedule these for resolution depending on their severity:\n - Critical: This sprint. \n - High: Next sprint. \n - Medium or Low: Add to backlog."
                }
              },
              {
                "type": "section",
                "text": {
                  "type": "mrkdwn",
                  "text": "<https://storage.googleapis.com/${{ secrets.GCP_SECURITY_REPORTS_BUCKET }}/rasa-sdk-dependency-report-${{ steps.find-tag.outputs.tag }}-${{ steps.date.outputs.date }}.txt|View Scan Report>"
                }
              }
            ]
          }
      env:
        SLACK_BOT_TOKEN: ${{ secrets.SLACK_CODESECURITY_BOT_TOKEN }}


Security Patching - SDK

name: Security Patching - SDK

on:
  schedule:
    # Run cron job at 8AM Monday to Sunday.
    - cron: '0 8 * * *'
  workflow_dispatch:

env:
  # Registry used to store Docker images used for release purposes.
  RELEASE_REGISTRY_SDK: "europe-west3-docker.pkg.dev/rasa-releases/rasa-sdk"
  GITHUB_WORKFLOW_URL: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"

permissions:
    id-token: write # This is required for requesting the JWT
    contents: read # This is required for actions/checkout

jobs:
  get_tags:
    runs-on: ubuntu-22.04
    continue-on-error: true
    outputs:
      tags: ${{ steps.tags.outputs.tags }}
    steps:
      # We have to check out the repo to be able to list the tags.
      - name: Checkout repository to check tags
        uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c

      - name: Set up Python
        uses: actions/setup-python@bd6b4b6205c4dbad673328db7b31b7fab9e241c0
        with:
          python-version: '3.9'

      - name: Fetch all tags
        run: git fetch --tags

      - name: Run Python script
        id: tags
        run: |
          python scripts/get_tags_from_branch.py '["3.3.x","3.4.x","3.5.x","3.6.x","3.7.x","3.8.x"]' '3.3.x'

      - name: Show tags
        run: |
          echo "Tags: ${{ steps.tags.outputs.tags }}"

  build:
    runs-on: ubuntu-22.04
    needs: get_tags
    # Don't allow a single image failure to block all the image builds.
    continue-on-error: true

    strategy:
      matrix:
        supported_versions: ${{ fromJson(needs.get_tags.outputs.tags) }}

    steps:
      # We have to check out the repo to be able to list the tags.
      - name: Checkout repository to check tags
        uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c

      # Determine the date in the right format for us to tag the image.
      - name: Get current date
        id: date
        run: echo "date=$(date +'%Y%m%d')" >> $GITHUB_OUTPUT

      # Authenticate with Gcloud.
      - name: Authenticate with gcloud for release registry 🎫
        id: 'auth-release'
        uses: 'google-github-actions/auth@ef5d53e30bbcd8d0836f4288f5e50ff3e086997d'
        with:
            workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
            service_account: '${{ secrets.RASA_SDK_RELEASE_ACCOUNT_NAME }}'

      # Authenticate with artifact registry where the images are stored.
      - name: Authenticate docker for release registry 🎫
        run: gcloud auth configure-docker europe-west3-docker.pkg.dev

      # Rebuild the Docker image for this micro. A line contained in the Dockerfile ensures that the latest OS patches are applied.
      # VERSION_NUMBER is passed into the build command to determine which Rasa OSS container image is used as the base for the Rasa Pro container.
      - name: Build and tag security patched image
        id: build
        continue-on-error: true
        run: |
          docker build . -t ${{env.RELEASE_REGISTRY_SDK}}/rasa-sdk:${{ matrix.supported_versions }}-latest --build-arg VERSION_NUMBER=${{ matrix.supported_versions }} -f Dockerfile.patch

      - name: Fail pushing the patch if the build is not a success
        if: steps.build.outcome == 'failure'
        run: exit 1

      # Push patched images to the release registry with patched tag.
      - name: Push image to release registry
        id: push
        run: |
          docker push ${{env.RELEASE_REGISTRY_SDK}}/rasa-sdk:${{ matrix.supported_versions }}-latest

      - name: Alert Slack if build fails
        if: steps.build.outcome == 'failure' || steps.push.outcome == 'failure'
        uses: slackapi/slack-github-action@007b2c3c751a190b6f0f040e47ed024deaa72844
        with:
          payload: |
            {
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "Docker image security patch for Rasa SDK version `${{ matrix.supported_versions }}` failed. Please check the <${{ env.GITHUB_WORKFLOW_URL }}|workflow log> for more information."
                  }
                }
              ]
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CODESECURITY_WEBHOOK_URL }}
          SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK


Security Scans

name: Security Scans

on:
  pull_request:
    types: [opened, synchronize, labeled]

jobs:

  trivy:
    name: Detecting hardcoded secrets
    runs-on: ubuntu-22.04
    steps:
    - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
      with:
        # Fetch all history for all tags and branches
        fetch-depth: '0'
    - name: Run Trivy vulnerability scanner
      id: trivy
      uses: aquasecurity/trivy-action@d63413b0a4a4482237085319f7f4a1ce99a8f2ac
      continue-on-error: true
      with:
        format: 'table'
        scan-type: 'fs'
        exit-code: '1'
        security-checks: 'secret'
    - name: Alert on secret finding
      if: steps.trivy.outcome == 'failure'
      uses: slackapi/slack-github-action@936158bbe252e9a6062e793ea4609642c966e302
      with:
        payload: |
          {
            "text": "*A secret was detected in a GitHub commit in the repo ${{ github.repository }}.*\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}",
            "blocks": [
              {
                "type": "section",
                "text": {
                  "type": "mrkdwn",
                  "text": "*A secret was detected in a GitHub commit in the repo ${{ github.repository }}.*\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}"
                }
              }
            ]
          }
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CODESECURITY_WEBHOOK_URL }}
        SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
    - name: Fail build if a secret is found
      if: steps.trivy.outcome == 'failure'
      run: |
        echo "=========================================================="
        echo "| This build has failed because Trivy detected a secret. |"
        echo "=========================================================="
        echo "1. Check the step 'Run Trivy vulnerability scanner' for output to help you find the secret."
        echo "2. If the finding is a false positive, add it as an entry to trivy-secret.yaml in the root of the repo to suppress the finding."
        echo "3. If the finding is valid, the security team can help advise your next steps."
        exit 1


Semgrep

# Name of this GitHub Actions workflow.
name: Semgrep

on:
  # Scan mainline branches and report all findings:
  push:
    branches:
        - main
  # Scan changed files in PRs (diff-aware scanning):
  pull_request:

jobs:
  semgrep:
    # User-definable name of this GitHub Actions job:
    name: Semgrep Workflow Security Scan
    # If you are self-hosting, change the following `runs-on` value:
    runs-on: ubuntu-22.04

    container:
      # A Docker image with Semgrep installed. Do not change this.
      image: returntocorp/semgrep@sha256:37736e4992c539f760e36e14d48924bd9fa70d0abbde39a6d86d93f66a1affd4

    # To skip any PR created by dependabot to avoid permission issues:
    if: (github.actor != 'dependabot[bot]')

    steps:
      # Fetch project source with GitHub Actions Checkout.
      - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3
      # Run the "semgrep ci" command on the command line of the docker image.
      - run: semgrep ci
        env:
           # Add the rules that Semgrep uses by setting the SEMGREP_RULES environment variable.
           SEMGREP_RULES: p/github-actions


Typo CI

name: Typo CI

on:
  push:
    branches-ignore:
      - main
jobs:
  spellcheck:
    name: Typo CI (GitHub Action)
    runs-on: ubuntu-22.04
    timeout-minutes: 4
    if: "!contains(github.event.head_commit.message, '[ci skip]')"
    steps:
    - name: TypoCheck
      uses: typoci/spellcheck-action@master
      # with:
        # A license can be purchased via:
        # https://gumroad.com/l/MvvBE
        # typo_ci_license_key: ${{ secrets.TYPO_CI_LICENSE_KEY }}
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


tests

name: tests

on:
  push:
    branches:
      - '*'
  pull_request:
    branches:
      - master

jobs:
  test-code-style:
    name: Code style tests
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
    steps:
      - uses: actions/checkout@v4
      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
          allow-prereleases: true
      - name: Install dependencies
        run: |
          pip install --upgrade pip
          pip install .
          pip install -r requirements-dev.txt
      - name: Run ruff
        run: ruff check mashumaro
      - name: Run mypy
        run: mypy mashumaro
      - name: Run black
        run: black --check .
      - name: Run codespell
        run: codespell mashumaro tests README.md .github/*.md
  test-posix:
    name: Tests on Posix
    needs:
      - test-code-style
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
    steps:
    - uses: actions/checkout@v4
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v5
      with:
        python-version: ${{ matrix.python-version }}
        allow-prereleases: true
    - name: Install dependencies
      run: |
        pip install --upgrade pip
        pip install .
        pip install -r requirements-dev.txt
    - name: Run tests with coverage
      run: pytest --cov=mashumaro --cov=tests
    - name: Upload Coverage
      run: coveralls --service=github
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        COVERALLS_FLAG_NAME: posix-${{ matrix.python-version }}
        COVERALLS_PARALLEL: true
  test-windows:
    name: Tests on Windows
    needs:
      - test-code-style
    runs-on: windows-latest
    strategy:
      matrix:
        python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
    steps:
    - uses: actions/checkout@v4
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v5
      with:
        python-version: ${{ matrix.python-version }}
        allow-prereleases: true
    - name: Install dependencies
      run: |
        pip install --upgrade pip
        pip install .
        pip install -r requirements-dev.txt
        pip install tzdata
    - name: Run tests with coverage
      run: pytest --cov=mashumaro --cov=tests
    - name: Upload Coverage
      run: coveralls --service=github
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        COVERALLS_FLAG_NAME: windows-${{ matrix.python-version }}
        COVERALLS_PARALLEL: true
  coveralls:
    name: Finish Coveralls
    needs:
    - test-posix
    - test-windows
    runs-on: ubuntu-latest
    steps:
    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version: 3.11
    - name: Install dependencies
      run: pip install coveralls
    - name: Finish coveralls
      run: coveralls --service=github --finish
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


rpaframework-assistant

name: rpaframework-assistant

on:
  push:
    branches:
      - master
    paths:
      - 'packages/assistant/**'
      - '.github/workflows/assistant.yaml'
  pull_request:
    branches:
      - master
    paths:
      - 'packages/assistant/**'
      - '.github/workflows/assistant.yaml'

defaults:
  run:
    working-directory: './packages/assistant'

jobs:
  test:
    runs-on: ${{ matrix.os }}

    strategy:
      fail-fast: false
      matrix:
        name: [
          "windows-py38",
          "windows-py39",
          "windows-py310",

          "ubuntu-py38",
          "ubuntu-py39",
          "ubuntu-py310",

          "macos-py38",
          "macos-py39",
          "macos-py310",
        ]

        include:
          - name: "windows-py38"
            python: "3.8"
            os: windows-latest
          - name: "windows-py39"
            python: "3.9"
            os: windows-latest
          - name: "windows-py310"
            python: "3.10"
            os: windows-latest
          - name: "ubuntu-py38"
            python: "3.8"
            os: ubuntu-latest
          - name: "ubuntu-py39"
            python: "3.9"
            os: ubuntu-latest
          - name: "ubuntu-py310"
            python: "3.10"
            os: ubuntu-latest
          - name: "macos-py38"
            python: "3.8"
            os: macos-latest
          - name: "macos-py39"
            python: "3.9"
            os: macos-latest
          - name: "macos-py310"
            python: "3.10"
            os: macos-latest

    env:
      INVOKE_IS_CI_CD: 1
    steps:
    - uses: actions/checkout@v2
    - uses: actions/setup-node@v2
      with:
        node-version: '14'
    - name: Set up Python ${{ matrix.python }}
      uses: actions/setup-python@v1
      with:
        python-version: ${{ matrix.python }}
    - name: Upgrade pip
      run: python -m pip install --upgrade pip
    - name: Install invocation prerequisites
      run: pip install -Ur ../../invocations/requirements.txt
    - name: Install invocation package
      run: invoke install-invocations || invoke self.install-invocations
    - name: Install dependencies
      run: invoke install
    - name: Lint
      run: invoke code.lint -e
    - name: Test
      run: invoke code.test -a
    # TODO: add graphical tests like we hhave on main

  publish:
    # Only publish on master workflow runs
    if: github.ref == 'refs/heads/master'
    needs: test
    runs-on: ubuntu-latest
    env:
      INVOKE_IS_CI_CD: 1

    steps:
    - uses: actions/checkout@v2
    - uses: actions/setup-node@v1
      with:
        node-version: 14.x
        registry-url: https://npm.pkg.github.com/
        scope: '@robocorp'
    - name: Set up Python
      uses: actions/setup-python@v1
      with:
        python-version: '3.9'
    - name: Upgrade pip
      run: python -m pip install --upgrade pip
    - name: Install invocation prerequisites
      run: pip install -Ur ../../invocations/requirements.txt
    - name: Install invocation package
      run: invoke install-invocations || invoke self.install-invocations
    - name: Install dependencies
      run: invoke install
    - name: Build and publish package
      if: success()
      run: |
        invoke install.setup-poetry --devpi-url ${{ secrets.devpiUrl }} --username ci --password ${{ secrets.devpiPassword }}
        invoke build.publish --ci --version=prerelease --yes-to-all