Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .deepsource.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,3 @@ enabled = true

[analyzers.meta]
runtime_version = "3.x.x"

68 changes: 68 additions & 0 deletions .github/workflows/backport.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Backport

on:
pull_request_target:
types: [closed, labeled]

permissions:
contents: write
pull-requests: write

jobs:
backport:
name: Backport PR
if: >
github.event.pull_request.merged == true
&& contains(join(github.event.pull_request.labels.*.name, ','), 'backport-to-')
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Create backport PR(s)
uses: korthout/backport-action@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
label_pattern: "^backport-to-(.+)$"
merge_commits: "skip"
pull_title: "[Backport ${target_branch}] ${pull_title}"
pull_description: |
Backport of #${pull_number} to `${target_branch}`.

---

${pull_description}
copy_requested_reviewers: true
copy_assignees: true
copy_labels_pattern: "^(?!backport-to-)"
auto_merge_enabled: ${{ contains(join(github.event.pull_request.labels.*.name, ','), 'backport-auto-merge') }}

- name: Report backport status
if: failure()
uses: actions/github-script@v7
with:
script: |
const { owner, repo } = context.repo;
const pullNumber = context.payload.pull_request.number;

await github.rest.issues.createComment({
owner,
repo,
issue_number: pullNumber,
body: [
"⚠️ **Backport failed!**",
"",
"Automatic backport could not be completed. This is usually caused by cherry-pick conflicts or a missing target branch.",
"",
"Manual backport example:",
"",
"```bash",
"git checkout <target-branch>",
`git cherry-pick -x ${context.payload.pull_request.merge_commit_sha}`,
"```",
].join("\\n"),
});
73 changes: 73 additions & 0 deletions .github/workflows/linter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Linters

on:
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: linters-propms-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
semgrep:
name: Frappe Linter
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip

- name: Download Semgrep rules
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules

- name: Install Semgrep
run: pip install semgrep

# Blocking: real bugs and security issues only
- name: Run Semgrep rules
run: |
semgrep scan --config ./frappe-semgrep-rules/rules \
--config r/python.lang.security \
--severity=ERROR --error propms

# Informational: style and i18n warnings, never fails the build
- name: Semgrep warnings (non-blocking)
if: always()
run: |
semgrep scan --config ./frappe-semgrep-rules/rules \
--config r/python.lang.security \
--severity=WARNING propms || true

deps-vulnerable-check:
name: Vulnerable Dependency Check
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.11"

- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/*requirements.txt', '**/pyproject.toml', '**/setup.py') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-

- name: Install and run pip-audit
run: |
pip install pip-audit
pip-audit --desc on .
31 changes: 31 additions & 0 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Pre-commit

on:
pull_request:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: precommit-propms-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
pre-commit:
name: pre-commit
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: pip

- uses: actions/setup-node@v4
with:
node-version: 20

- uses: pre-commit/action@v3.0.1
31 changes: 31 additions & 0 deletions .github/workflows/semantic-commits.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Semantic Commits

on:
pull_request: {}

permissions:
contents: read

concurrency:
group: commitcheck-propms-${{ github.event.number }}
cancel-in-progress: true

jobs:
commitlint:
name: Check Commit Messages
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 200

- uses: actions/setup-node@v4
with:
node-version: 20
check-latest: true

- name: Check commit messages
run: |
npm install @commitlint/cli @commitlint/config-conventional conventional-changelog-conventionalcommits
npx commitlint --verbose --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }}
178 changes: 178 additions & 0 deletions .github/workflows/tag-and-promote-from-pr-label.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
name: Tag and promote from PR label

on:
pull_request_target:
types:
- closed
- labeled

permissions:
contents: write
pull-requests: read

jobs:
tag-and-promote:
if: >
github.event.pull_request.merged == true &&
(
github.event.action == 'closed' ||
(
github.event.action == 'labeled' &&
startsWith(github.event.label.name, 'promote/')
)
)
runs-on: ubuntu-latest

steps:
- name: Determine target branch from PR labels
id: target
uses: actions/github-script@v8
with:
script: |
const labels = context.payload.pull_request.labels.map(label => label.name);

const mapping = {
"promote/version-15": "version-15",
"promote/version-16": "version-16",
"promote/production": "production"
};

const matchedLabels = labels.filter(label => mapping[label]);

if (matchedLabels.length === 0) {
core.info(
`No promote target label found. Skipping promotion. Add one of: ${Object.keys(mapping).join(", ")}`
);
core.setOutput("should_promote", "false");
return;
}

if (matchedLabels.length > 1) {
core.setFailed(
`Multiple promote target labels found: ${matchedLabels.join(", ")}. Keep only one.`
);
return;
}

const matchedLabel = matchedLabels[0];

core.setOutput("should_promote", "true");
core.setOutput("target_branch", mapping[matchedLabel]);
core.setOutput("matched_label", matchedLabel);

- name: Checkout merged commit
if: steps.target.outputs.should_promote == 'true'
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.merge_commit_sha }}
fetch-depth: 0

- name: Read version from propms.__version__
if: steps.target.outputs.should_promote == 'true'
id: version
shell: bash
run: |
VERSION=$(python - <<'PY'
import re
from pathlib import Path

init_file = Path("propms/__init__.py")
content = init_file.read_text()

match = re.search(r'^__version__\s*=\s*["\']([^"\']+)["\']', content, re.M)

if not match:
raise SystemExit("Could not find __version__ in propms/__init__.py")

print(match.group(1))
PY
)

echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "tag=v$VERSION" >> "$GITHUB_OUTPUT"

- name: Configure git user
if: steps.target.outputs.should_promote == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

- name: Create and push version tag
if: steps.target.outputs.should_promote == 'true'
shell: bash
run: |
git fetch --tags

TAG="${{ steps.version.outputs.tag }}"
CURRENT_COMMIT="$(git rev-parse HEAD)"

if git rev-parse "$TAG" >/dev/null 2>&1; then
TAG_COMMIT="$(git rev-list -n 1 "$TAG")"

if [ "$TAG_COMMIT" != "$CURRENT_COMMIT" ]; then
echo "Tag $TAG already exists but points to $TAG_COMMIT, not current merged commit $CURRENT_COMMIT."
exit 1
fi

echo "Tag $TAG already exists and points to the current merged commit. Skipping tag creation."
exit 0
fi

git tag -a "$TAG" -m "Release $TAG"
git push origin "$TAG"

- name: Create GitHub release with generated title and notes
if: steps.target.outputs.should_promote == 'true'
uses: actions/github-script@v8
env:
TAG_NAME: ${{ steps.version.outputs.tag }}
TARGET_COMMITISH: ${{ github.event.pull_request.merge_commit_sha }}
with:
script: |
const tagName = process.env.TAG_NAME;
const targetCommitish = process.env.TARGET_COMMITISH;
const { owner, repo } = context.repo;

try {
const existingRelease = await github.rest.repos.getReleaseByTag({
owner,
repo,
tag: tagName
});

core.info(
`Release already exists for ${tagName}: ${existingRelease.data.html_url}. Skipping release creation.`
);
return;
} catch (error) {
if (error.status !== 404) {
throw error;
}
}

const generatedNotes = await github.rest.repos.generateReleaseNotes({
owner,
repo,
tag_name: tagName,
target_commitish: targetCommitish,
previous_tag_name: undefined
});

const release = await github.rest.repos.createRelease({
owner,
repo,
tag_name: tagName,
target_commitish: targetCommitish,
name: generatedNotes.data.name,
body: generatedNotes.data.body,
draft: false,
prerelease: false
});

core.info(`Created release: ${release.data.html_url}`);

- name: Promote merged commit to target branch
if: steps.target.outputs.should_promote == 'true'
shell: bash
run: |
git push origin HEAD:${{ steps.target.outputs.target_branch }}
Loading
Loading