snapshot of https://github.com/frappe/crm.git (develop, 2026-09-18; blobs > 131072B omitted)
Some checks failed
Frontend / Unit Tests & Coverage (push) Has been skipped
Regenerate POT file (translatable strings) / Regenerate POT file (develop) (push) Failing after 1m13s
Regenerate POT file (translatable strings) / Regenerate POT file (main-hotfix) (push) Failing after 6s
Server / Tests (frappe ${{ matrix.frappe-branch }}) (push) Has been skipped
Server / Resolve frappe matrix (push) Failing after 1s
Frontend / Lint (push) Failing after 1s
Frontend / Production Build (push) Failing after 1s
Server / Coverage Wrap Up (push) Has been skipped
Frontend / Coverage Wrap Up (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-18 01:36:01 +05:00
commit db69de1c11
905 changed files with 102713 additions and 0 deletions

38
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,38 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: 'bug'
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,43 @@
name: Run Frontend Tests
description: Install frontend dependencies and run Vitest with coverage.
inputs:
node-version:
description: Node.js version to use
required: false
default: "24"
enable-coverage:
description: When true, run with coverage collection
required: false
default: "true"
working-directory:
description: Path to the frontend directory
required: false
default: "frontend"
outputs:
coverage-path:
description: Path to the generated lcov coverage file
value: ${{ steps.set-output.outputs.coverage-path }}
runs:
using: composite
steps:
- name: Install dependencies
shell: bash
working-directory: ${{ inputs.working-directory }}
run: yarn install --frozen-lockfile
- name: Run tests (with coverage)
if: inputs.enable-coverage == 'true'
shell: bash
working-directory: ${{ inputs.working-directory }}
run: yarn test:coverage
- name: Run tests (without coverage)
if: inputs.enable-coverage != 'true'
shell: bash
working-directory: ${{ inputs.working-directory }}
run: yarn test:run
- name: Set coverage output path
id: set-output
shell: bash
run: echo "coverage-path=${{ inputs.working-directory }}/coverage/lcov.info" >> "$GITHUB_OUTPUT"

View File

@@ -0,0 +1,28 @@
name: Publish Coverage to Codecov
description: Upload downloaded coverage artifacts to Codecov.
inputs:
name:
description: Coverage upload name in Codecov
required: false
default: "MariaDB"
token:
description: Codecov upload token
required: true
fail-ci-if-error:
description: Fail workflow if Codecov upload fails
required: false
default: "true"
verbose:
description: Enable verbose Codecov logging
required: false
default: "true"
runs:
using: composite
steps:
- name: Upload coverage data
uses: codecov/codecov-action@v4
with:
name: ${{ inputs.name }}
token: ${{ inputs.token }}
fail_ci_if_error: ${{ inputs.fail-ci-if-error }}
verbose: ${{ inputs.verbose }}

View File

@@ -0,0 +1,42 @@
name: Run CRM Server Tests
description: Execute bench tests for the CRM app with optional coverage collection.
inputs:
bench-path:
description: Absolute path to the bench directory
required: false
default: "/home/runner/frappe-bench"
site:
description: Bench site on which to run tests
required: false
default: "test_site"
app:
description: App to test
required: false
default: "crm"
enable-coverage:
description: When true, include coverage flag during test execution
required: false
default: "true"
type:
description: Bench command type environment variable
required: false
default: "server"
ci-build-id:
description: CI build identifier propagated to bench commands
required: false
runs:
using: composite
steps:
- name: Run Tests
shell: bash
working-directory: ${{ inputs.bench-path }}
env:
TYPE: ${{ inputs.type }}
CI_BUILD_ID: ${{ inputs.ci-build-id }}
run: |
bench --site "${{ inputs.site }}" set-config allow_tests true
if [ "${{ inputs.enable-coverage }}" = "true" ]; then
bench --site "${{ inputs.site }}" run-tests --app "${{ inputs.app }}" --coverage
else
bench --site "${{ inputs.site }}" run-tests --app "${{ inputs.app }}"
fi

View File

@@ -0,0 +1,95 @@
name: Setup Server Environment
description: Prepare Python, Node, caching, and bench prerequisites for CRM server tests.
inputs:
python-version:
description: Python version to install
required: false
default: "3.14"
node-version:
description: Node version to install
required: false
default: "24"
type:
description: Bench install type passed to install script
required: false
default: "server"
install-erpnext:
description: When "true", fetch and install erpnext alongside crm
required: false
default: "false"
frappe-branch:
description: Frappe (and erpnext) branch to install and test against
required: false
default: "develop"
runs:
using: composite
steps:
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: ${{ inputs.python-version }}
- name: Check for valid Python & merge conflicts
shell: bash
run: |
python -m compileall -f "${GITHUB_WORKSPACE}"
if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"; then
echo "Found merge conflicts"
exit 1
fi
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: ${{ inputs.node-version }}
check-latest: true
- name: Install Yarn
shell: bash
run: npm install -g yarn
- name: Add to hosts file
shell: bash
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- name: Cache node modules
uses: actions/cache@v4
env:
cache-name: cache-node-modules
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Get yarn cache directory path
id: yarn-cache-dir-path
shell: bash
run: echo "dir=$(yarn cache dir)" >> "${GITHUB_OUTPUT}"
- name: Cache yarn
uses: actions/cache@v4
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install bench dependencies
shell: bash
env:
TYPE: ${{ inputs.type }}
INSTALL_ERPNEXT: ${{ inputs.install-erpnext }}
FRAPPE_BRANCH: ${{ inputs.frappe-branch }}
run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh

View File

@@ -0,0 +1,18 @@
name: Upload Coverage Artifact
description: Persist coverage artifacts produced during CRM server testing.
inputs:
artifact-name:
description: Name assigned to the uploaded artifact
required: false
default: "coverage-server"
coverage-path:
description: Path to the coverage XML or directory to upload
required: true
runs:
using: composite
steps:
- name: Upload coverage data
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.artifact-name }}
path: ${{ inputs.coverage-path }}

63
.github/helper/install.sh vendored Normal file
View File

@@ -0,0 +1,63 @@
#!/bin/bash
set -e
cd ~ || ex
sudo apt update
sudo apt remove mysql-server mysql-client
sudo apt install libcups2-dev redis-server mariadb-client libmariadb-dev
# Frappe (and erpnext) branch to test against; set by CI matrix, defaults to develop.
FRAPPE_BRANCH="${FRAPPE_BRANCH:-develop}"
pip install frappe-bench
git clone "https://github.com/frappe/frappe" --branch "${FRAPPE_BRANCH}" --depth 1
bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench
mkdir ~/frappe-bench/sites/test_site
cp -r "${GITHUB_WORKSPACE}/.github/helper/site_config.json" ~/frappe-bench/sites/test_site/site_config.json
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE DATABASE test_frappe"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "FLUSH PRIVILEGES"
install_whktml() {
wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf
sudo chmod o+x /usr/local/bin/wkhtmltopdf
}
install_whktml &
cd ~/frappe-bench || exit
sed -i 's/watch:/# watch:/g' Procfile
sed -i 's/schedule:/# schedule:/g' Procfile
sed -i 's/socketio:/# socketio:/g' Procfile
sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
bench get-app crm "${GITHUB_WORKSPACE}"
# Only pull erpnext when the integration is under test, to keep other runs fast.
if [ "${INSTALL_ERPNEXT}" = "true" ]; then
bench get-app erpnext --branch "${FRAPPE_BRANCH}"
fi
bench setup requirements --dev
bench start &>> ~/frappe-bench/bench_start.log &
CI=Yes bench build --app frappe &
bench --site test_site reinstall --yes
if [ "${INSTALL_ERPNEXT}" = "true" ]; then
bench --verbose --site test_site install-app erpnext crm
else
bench --verbose --site test_site install-app crm
fi

16
.github/helper/site_config.json vendored Normal file
View File

@@ -0,0 +1,16 @@
{
"db_host": "127.0.0.1",
"db_port": 3306,
"db_name": "test_frappe",
"db_password": "test_frappe",
"auto_email_id": "test@example.com",
"mail_server": "smtp.example.com",
"mail_login": "test@example.com",
"mail_password": "test",
"admin_password": "admin",
"root_login": "root",
"root_password": "root",
"host_name": "http://test_site:8000",
"install_apps": ["crm"],
"throttle_user_limit": 100
}

59
.github/helper/update_pot_file.sh vendored Normal file
View File

@@ -0,0 +1,59 @@
#!/bin/bash
set -e
cd ~ || exit
echo "Setting Up Bench..."
pip install frappe-bench
bench -v init frappe-bench --skip-assets --skip-redis-config-generation --python "$(which python)" --frappe-branch develop
cd ./frappe-bench || exit
echo "Get FCRM..."
bench get-app --skip-assets crm "${GITHUB_WORKSPACE}"
echo "Generating POT file..."
bench generate-pot-file --app crm
cd ./apps/crm || exit
echo "Configuring git user..."
git config user.email "developers@erpnext.com"
git config user.name "frappe-pr-bot"
echo "Setting the correct git remote..."
# Here, the git remote is a local file path by default. Let's change it to the upstream repo.
git remote set-url upstream https://github.com/frappe/crm.git
gh auth setup-git
# One stable branch, not a dated one. The POT file is fully regenerated every run, so a
# fresh dated branch/PR each week just piles up and conflicts once any one is merged. Reusing
# a single branch (reset to the base tip, force-pushed) keeps exactly one PR that updates in
# place and can never conflict, since it's always rebuilt from the current base.
branch_name="pot_${BASE_BRANCH}"
echo "Resetting ${branch_name} onto ${BASE_BRANCH}..."
generated_pot=$(mktemp)
cp crm/locale/main.pot "${generated_pot}"
git fetch upstream "${BASE_BRANCH}"
git checkout -f -B "${branch_name}" "upstream/${BASE_BRANCH}"
cp "${generated_pot}" crm/locale/main.pot
git add crm/locale/main.pot
if git diff --cached --quiet; then
echo "No POT changes; nothing to do."
exit 0
fi
echo "Commiting changes..."
git commit -m "chore: update POT file"
echo "Pushing ${branch_name}..."
git push -u upstream "${branch_name}" --force
if gh pr view "${branch_name}" -R frappe/crm --json state -q '.state' 2>/dev/null | grep -q OPEN; then
echo "PR already open for ${branch_name}; force-push updated it."
else
echo "Creating a PR..."
gh pr create --fill --base "${BASE_BRANCH}" --head "${branch_name}" -R frappe/crm
fi

BIN
.github/logo.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

4
.github/logo.svg vendored Normal file
View File

@@ -0,0 +1,4 @@
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M214.286 0H85.7143C38.3756 0 0 38.3756 0 85.7143V214.286C0 261.624 38.3756 300 85.7143 300H214.286C261.624 300 300 261.624 300 214.286V85.7143C300 38.3756 261.624 0 214.286 0Z" fill="#EF0BF5"/>
<path d="M64.2141 90.301V111.862H214.339V140.214L160.187 193.146V208.993L139.705 208.885V193.146L85.6605 140.214H64.2141V149.269L118.259 202.202V230.23L181.634 230.769V202.202L235.786 149.269V90.301H64.2141Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 534 B

65
.github/workflows/builds.yml vendored Normal file
View File

@@ -0,0 +1,65 @@
name: Build Container Image
on:
workflow_dispatch:
push:
branches:
- main
tags:
- "*"
jobs:
build:
name: Build
runs-on: ubuntu-latest
strategy:
matrix:
arch: [amd64, arm64]
steps:
- name: Checkout Entire Repository
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/${{ matrix.arch }}
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set Branch
run: |
export APPS_JSON='[{"url": "https://github.com/frappe/crm","branch": "main"}]'
echo "APPS_JSON_BASE64=$(echo $APPS_JSON | base64 -w 0)" >> $GITHUB_ENV
echo "FRAPPE_BRANCH=version-15" >> $GITHUB_ENV
- name: Set Image Tag
run: |
echo "IMAGE_TAG=stable" >> $GITHUB_ENV
- uses: actions/checkout@v4
with:
repository: frappe/frappe_docker
path: builds
- name: Build and push
uses: docker/build-push-action@v6
with:
push: true
context: builds
file: builds/images/layered/Containerfile
tags: >
ghcr.io/${{ github.repository }}:${{ github.ref_name }},
ghcr.io/${{ github.repository }}:${{ env.IMAGE_TAG }}
build-args: |
"FRAPPE_BRANCH=${{ env.FRAPPE_BRANCH }}"
"APPS_JSON_BASE64=${{ env.APPS_JSON_BASE64 }}"

125
.github/workflows/frontend-tests.yml vendored Normal file
View File

@@ -0,0 +1,125 @@
name: Frontend
on:
pull_request:
paths-ignore:
- "**.md"
- "**.py"
- "**.txt"
- "**.csv"
- "**.po"
- "**.pot"
- ".github/workflows/server-tests.yml"
- ".github/workflows/builds.yml"
schedule:
# Run every day at midnight UTC / 5:30 IST
- cron: "0 0 * * *"
workflow_dispatch:
concurrency:
group: frontend-${{ github.event.number || github.ref }}
cancel-in-progress: true
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# frontend/package.json links `@framework/ui` to a sibling frappe checkout
# (link:../../frappe/ui); without it, anything importing from @framework/ui
# fails to resolve. actions/checkout cannot place a repo outside the
# workspace, and only the `ui` package is needed.
- name: Clone frappe for @framework/ui
run: |
git clone --depth 1 --branch develop --filter=blob:none --sparse \
https://github.com/frappe/frappe "$GITHUB_WORKSPACE/../frappe"
git -C "$GITHUB_WORKSPACE/../frappe" sparse-checkout set ui
- uses: actions/setup-node@v4
with:
node-version: "24"
cache: yarn
cache-dependency-path: frontend/yarn.lock
- name: Install dependencies
run: cd frontend && yarn install --frozen-lockfile
- name: ESLint
run: cd frontend && npx eslint src --ext .js,.ts,.vue
- name: Oxlint
run: cd frontend && npx oxlint src
build:
name: Production Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# see the lint job: @framework/ui links to a sibling frappe checkout
- name: Clone frappe for @framework/ui
run: |
git clone --depth 1 --branch develop --filter=blob:none --sparse \
https://github.com/frappe/frappe "$GITHUB_WORKSPACE/../frappe"
git -C "$GITHUB_WORKSPACE/../frappe" sparse-checkout set ui
- uses: actions/setup-node@v4
with:
node-version: "24"
cache: yarn
cache-dependency-path: frontend/yarn.lock
- name: Install dependencies
run: cd frontend && yarn install --frozen-lockfile
- name: Build frontend
run: cd frontend && yarn build
test:
name: Unit Tests & Coverage
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "24"
cache: yarn
cache-dependency-path: frontend/yarn.lock
- name: Run frontend tests
uses: ./.github/actions/frontend-tests
with:
node-version: "24"
enable-coverage: "true"
- name: Upload coverage artifact
if: github.base_ref == 'develop' || github.ref_name == 'develop'
uses: ./.github/actions/upload-coverage
with:
artifact-name: coverage-frontend
coverage-path: frontend/coverage/lcov.info
coverage:
name: Coverage Wrap Up
needs: test
if: github.base_ref == 'develop' || github.ref_name == 'develop'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download artifacts
uses: actions/download-artifact@v4
- name: Publish coverage to Codecov
uses: ./.github/actions/publish-codecov
with:
name: Frontend
token: ${{ secrets.CODECOV_TOKEN }}

40
.github/workflows/generate-pot-file.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: Regenerate POT file (translatable strings)
on:
schedule:
# 9:30 UTC => 3 PM IST Sunday
- cron: "30 9 * * 0"
workflow_dispatch:
jobs:
regenerate-pot-file:
name: Regenerate POT file
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
branch: ["develop", "main-hotfix"]
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ matrix.branch }}
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.14"
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 24
- name: Run script to update POT file
run: |
bash ${GITHUB_WORKSPACE}/.github/helper/update_pot_file.sh
env:
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
BASE_BRANCH: ${{ matrix.branch }}

85
.github/workflows/linters.yml vendored Normal file
View File

@@ -0,0 +1,85 @@
name: Linters
on:
pull_request:
workflow_dispatch:
permissions:
contents: read
pull-requests: read
jobs:
commit-lint:
name: Semantic Commits
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 200
- uses: actions/setup-node@v6
with:
node-version: "24"
check-latest: true
- name: Install commitlint
run: npm install --no-save @commitlint/cli @commitlint/config-conventional
- name: Check commit titles
run: npx commitlint --verbose --from "${{ github.event.pull_request.base.sha }}" --to "${{ github.event.pull_request.head.sha }}"
semgrep:
name: Semgrep Rules
runs-on: ubuntu-latest
env:
PIP_DISABLE_PIP_VERSION_CHECK: "1"
PYTHONDONTWRITEBYTECODE: "1"
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.14"
cache: pip
- name: Install Semgrep
run: pip install --upgrade semgrep
- name: Download Semgrep rules
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
- name: Run Semgrep rules
run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
pre-commit:
name: Pre-commit Checks
runs-on: ubuntu-latest
env:
PIP_DISABLE_PIP_VERSION_CHECK: "1"
PYTHONDONTWRITEBYTECODE: "1"
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.14"
cache: pip
- uses: actions/setup-node@v6
with:
node-version: "24"
- name: Install frontend dependencies
run: cd frontend && yarn install
- name: Run Pre-commit
uses: pre-commit/action@v3.0.1
with:
extra_args: --all-files

107
.github/workflows/migration-test.yml vendored Normal file
View File

@@ -0,0 +1,107 @@
name: Migration
# Verifies that an existing site on the PR's base branch can `bench migrate`
# to the PR code. Catches broken patches / schema changes before release.
on:
pull_request:
paths-ignore:
- "**.css"
- "**.js"
- "**.md"
- "**.html"
- "**.csv"
- "**.po"
- "**.pot"
workflow_dispatch:
concurrency:
group: migration-${{ github.event.number || github.ref }}
cancel-in-progress: true
jobs:
migrate:
name: Upgrade base branch -> PR
runs-on: ubuntu-latest
timeout-minutes: 60
services:
mysql:
image: mariadb:10.6
env:
MYSQL_ROOT_PASSWORD: root
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
env:
BASE_BRANCH: ${{ github.base_ref || github.ref_name }}
BENCH_PATH: /home/runner/frappe-bench
steps:
- name: Checkout PR code
uses: actions/checkout@v6
with:
path: pr-crm
- name: Resolve frappe branch for base
id: frappe
run: |
# Stable lanes run on frappe version-15; develop tracks frappe develop.
case "$BASE_BRANCH" in
main|main-hotfix) echo "branch=version-15" >> "$GITHUB_OUTPUT" ;;
*) echo "branch=develop" >> "$GITHUB_OUTPUT" ;;
esac
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: "3.14"
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: "24"
- name: Install system dependencies
run: |
sudo apt update
sudo apt install -y libcups2-dev redis-server mariadb-client libmariadb-dev
npm install -g yarn
- name: Configure database
run: |
echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE USER 'test_frappe'@'localhost' IDENTIFIED BY 'test_frappe'"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "CREATE DATABASE test_frappe"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "GRANT ALL PRIVILEGES ON \`test_frappe\`.* TO 'test_frappe'@'localhost'"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "FLUSH PRIVILEGES"
- name: Init bench with frappe
run: |
pip install frappe-bench
git clone "https://github.com/frappe/frappe" --branch "${{ steps.frappe.outputs.branch }}" --depth 1 ~/frappe
bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" "$BENCH_PATH"
- name: Install CRM at base branch
working-directory: /home/runner/frappe-bench
run: |
mkdir sites/test_site
cp "${GITHUB_WORKSPACE}/pr-crm/.github/helper/site_config.json" sites/test_site/site_config.json
sed -i 's/watch:/# watch:/g; s/schedule:/# schedule:/g; s/socketio:/# socketio:/g; s/redis_socketio:/# redis_socketio:/g' Procfile
bench get-app crm "https://github.com/frappe/crm" --branch "$BASE_BRANCH"
bench setup requirements --dev
bench start &>> "$BENCH_PATH/bench_start.log" &
CI=Yes bench build --app frappe &
bench --site test_site reinstall --yes
bench --verbose --site test_site install-app crm
- name: Upgrade CRM to PR code and migrate
working-directory: /home/runner/frappe-bench
run: |
# Swap the installed (base branch) code for the PR code, keep the git dir.
rsync -a --delete --exclude=".git" "${GITHUB_WORKSPACE}/pr-crm/" apps/crm/
bench setup requirements --dev
bench --site test_site migrate

32
.github/workflows/on_release.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Generate Semantic Release
on:
workflow_dispatch:
push:
branches:
- main
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout Entire Repository
uses: actions/checkout@v6
with:
fetch-depth: 0
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
- name: Setup dependencies
run: |
npm install @semantic-release/git @semantic-release/exec --no-save
- name: Create Release
env:
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
GIT_AUTHOR_NAME: "Frappe PR Bot"
GIT_AUTHOR_EMAIL: "developers@frappe.io"
GIT_COMMITTER_NAME: "Frappe PR Bot"
GIT_COMMITTER_EMAIL: "developers@frappe.io"
run: npx semantic-release

40
.github/workflows/release_notes.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
# This action:
#
# 1. Generates release notes using github API.
# 2. Strips unnecessary info like chore/style etc from notes.
# 3. Updates release info.
name: 'Release Notes'
on:
workflow_dispatch:
inputs:
tag_name:
description: 'Tag of release like v1.0.0'
required: true
type: string
release:
types: [released]
permissions:
contents: read
jobs:
regen-notes:
name: 'Regenerate release notes'
runs-on: ubuntu-latest
steps:
- name: Update notes
run: |
NEW_NOTES=$(gh api --method POST -H "Accept: application/vnd.github+json" /repos/frappe/crm/releases/generate-notes -f tag_name=$RELEASE_TAG \
| jq -r '.body' \
| sed -E '/^\* (chore|ci|test|docs|style)/d' \
| sed -E 's/by @mergify //'
)
RELEASE_ID=$(gh api -H "Accept: application/vnd.github+json" /repos/frappe/crm/releases/tags/$RELEASE_TAG | jq -r '.id')
gh api --method PATCH -H "Accept: application/vnd.github+json" /repos/frappe/crm/releases/$RELEASE_ID -f body="$NEW_NOTES"
env:
GH_TOKEN: ${{ secrets.RELEASE_TOKEN }}
RELEASE_TAG: ${{ github.event.inputs.tag_name || github.event.release.tag_name }}

106
.github/workflows/server-tests.yml vendored Normal file
View File

@@ -0,0 +1,106 @@
name: Server
on:
pull_request:
paths-ignore:
- "**.css"
- "**.js"
- "**.md"
- "**.html"
- "**.csv"
- "**.po"
- "**.pot"
schedule:
# Run everday at midnight UTC / 5:30 IST
- cron: "0 0 * * *"
env:
CRM_BRANCH: ${{ github.base_ref || github.ref_name }}
concurrency:
group: develop-crm-${{ github.event.number }}
cancel-in-progress: true
jobs:
setup:
name: Resolve frappe matrix
runs-on: ubuntu-latest
outputs:
frappe-branches: ${{ steps.resolve.outputs.branches }}
steps:
- name: Resolve frappe branches from target branch
id: resolve
run: |
BASE="${{ github.base_ref || github.ref_name }}"
# develop tracks frappe develop; the stable lane ships frappe 15 and 16.
if [ "$BASE" = "develop" ]; then
echo 'branches=["develop"]' >> "$GITHUB_OUTPUT"
else
echo 'branches=["version-15","version-16"]' >> "$GITHUB_OUTPUT"
fi
test:
name: Tests (frappe ${{ matrix.frappe-branch }})
needs: setup
runs-on: ubuntu-latest
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
frappe-branch: ${{ fromJson(needs.setup.outputs.frappe-branches) }}
services:
mysql:
image: mariadb:10.6
env:
MYSQL_ROOT_PASSWORD: root
ports:
- 3306:3306
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
steps:
- name: Clone
uses: actions/checkout@v6
- name: Setup environment
uses: ./.github/actions/setup-server-env
with:
python-version: "3.14"
node-version: "24"
type: "server"
# erpnext: stable lane only, never v16 (v16 test bootstrap broken on bare bench)
install-erpnext: ${{ (github.base_ref == 'main-hotfix' || github.base_ref == 'main') && matrix.frappe-branch != 'version-16' }}
frappe-branch: ${{ matrix.frappe-branch }}
- name: Run server tests
uses: ./.github/actions/run-server-tests
with:
bench-path: "/home/runner/frappe-bench"
site: "test_site"
app: "crm"
enable-coverage: "true"
type: "server"
ci-build-id: ${{ github.run_id }}
- name: Persist coverage artifact
if: github.base_ref == 'develop' || github.ref_name == 'develop'
uses: ./.github/actions/upload-coverage
with:
artifact-name: coverage-${{ matrix.frappe-branch }}
coverage-path: /home/runner/frappe-bench/sites/coverage.xml
coverage:
name: Coverage Wrap Up
needs: test
if: github.base_ref == 'develop' || github.ref_name == 'develop'
runs-on: ubuntu-latest
steps:
- name: Clone
uses: actions/checkout@v6
- name: Download artifacts
uses: actions/download-artifact@v4
- name: Publish coverage
uses: ./.github/actions/publish-codecov
with:
token: ${{ secrets.CODECOV_TOKEN }}

181
.github/workflows/ui-tests.yml vendored Normal file
View File

@@ -0,0 +1,181 @@
name: UI Tests
# E2E (Playwright) suite. Heavy gate: runs on the main-hotfix staging lane
# before promoting to main, plus on demand. Develop stays on the fast lane.
on:
push:
branches:
- main-hotfix
pull_request:
branches:
- main-hotfix
workflow_dispatch:
concurrency:
group: ui-tests-crm-${{ github.event.number || github.ref }}
cancel-in-progress: true
env:
FRAPPE_BRANCH: develop
jobs:
ui-tests:
runs-on: ubuntu-latest
timeout-minutes: 60
name: Playwright E2E Tests
services:
redis-cache:
image: redis:alpine
ports:
- 13000:6379
redis-queue:
image: redis:alpine
ports:
- 11000:6379
mariadb:
image: mariadb:10.6
env:
MYSQL_ROOT_PASSWORD: root
ports:
- 3306:3306
options: --health-cmd="mariadb-admin ping" --health-interval=5s --health-timeout=2s --health-retries=3
steps:
- name: Clone
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
check-latest: true
- name: Add to Hosts
run: echo "127.0.0.1 crm.test" | sudo tee -a /etc/hosts
- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: 'echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT'
- name: Cache yarn
uses: actions/cache@v4
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Cache Playwright browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('package.json') }}
restore-keys: |
${{ runner.os }}-playwright-
- name: Install MariaDB Client
run: |
sudo apt update
sudo apt-get install mariadb-client
- name: Setup Bench
run: |
pip install frappe-bench
bench init --skip-redis-config-generation --skip-assets --frappe-branch "$FRAPPE_BRANCH" --python "$(which python)" ~/frappe-bench
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL character_set_server = 'utf8mb4'"
mariadb --host 127.0.0.1 --port 3306 -u root -proot -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'"
- name: Install CRM
working-directory: /home/runner/frappe-bench
run: |
bench get-app crm $GITHUB_WORKSPACE
bench setup requirements --dev
bench new-site --db-root-password root --admin-password admin crm.test
bench --site crm.test install-app crm
bench build --app crm
env:
CI: "Yes"
- name: Configure Site for UI Tests
working-directory: /home/runner/frappe-bench
run: |
bench --site crm.test set-config allow_tests true
bench --site crm.test set-config host_name "http://crm.test:8000"
# Dummy default-outgoing account so send-email queues without real SMTP.
bench --site crm.test console <<'PY'
import frappe
if not frappe.db.exists("Email Account", {"default_outgoing": 1}):
frappe.get_doc({
"doctype": "Email Account",
"email_account_name": "E2E Outgoing",
"email_id": "e2e@example.com",
"smtp_server": "localhost",
"smtp_port": 25,
"enable_outgoing": 1,
"default_outgoing": 1,
"no_smtp_authentication": 1,
}).insert(ignore_permissions=True)
frappe.db.commit()
PY
- name: Start Frappe Server
working-directory: /home/runner/frappe-bench
run: |
sed -i 's/^watch:/# watch:/g' Procfile
sed -i 's/^schedule:/# schedule:/g' Procfile
bench start &> bench_start.log &
echo "Waiting for Frappe server to start..."
timeout 60 bash -c 'until curl -s http://crm.test:8000 > /dev/null; do sleep 2; done'
echo "Frappe server is ready!"
- name: Install Playwright
run: |
npm install --ignore-scripts
npx playwright install --with-deps chromium
- name: Run Playwright Tests
run: npx playwright test
env:
BASE_URL: http://crm.test:8000
FRAPPE_USER: Administrator
FRAPPE_PASSWORD: admin
- name: Upload Playwright Report
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 7
- name: Upload Test Results
uses: actions/upload-artifact@v4
if: failure()
with:
name: test-results
path: test-results/
retention-days: 7
- name: Show Bench Logs on Failure
if: failure()
working-directory: /home/runner/frappe-bench
run: |
echo "=== Bench Start Log ==="
cat bench_start.log || true
echo ""
echo "=== Frappe Logs ==="
cat logs/*.log || true