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

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