Skip to content
Draft
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
8 changes: 8 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
Thanks for contributing! To help us review this effectively, please ensure this PR is linked to a pre-approved issue. PRs without a linked issue may be paused pending discussion — see [CONTRIBUTING.md](../CONTRIBUTING.md) for details.

> **Signed commits:** We use signed commits to verify our contributors — it's a great security best practice! If you haven't set this up yet, see [GitHub's guide on signing commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits).

---

## Description

<!-- What do we want to achieve with this PR? -->
Expand Down Expand Up @@ -27,10 +33,12 @@ Please provide step-by-step instructions to test the changes.

<!-- Check these after PR creation -->

- [ ] I've discussed this change in an issue before creating this PR.
- [ ] I have thoroughly tested this code to the best of my abilities.
- [ ] I have reviewed the code myself before requesting a review.
- [ ] This code is covered by unit tests to verify that it works as intended.
- [ ] The QA of this PR is done by a member of the QA team (to be checked by QA).
- [ ] My commits are signed (see the hint at the top of this template).

<!--
Example:
Expand Down
159 changes: 159 additions & 0 deletions .github/workflows/gatekeeper.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
name: PR Gatekeeper

on:
pull_request:
types: [opened, synchronize, reopened, edited]

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

jobs:
check-pr-requirements:
name: Check Linked Issue & Commit Signatures
runs-on: ubuntu-latest
steps:
- name: Validate PR requirements
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const body = pr.body || '';

// Internal contributors (owners, org members, collaborators) are exempt
// from the linked-issue requirement — it only applies to external developers.
const internalRoles = ['OWNER', 'MEMBER', 'COLLABORATOR'];
const isInternal = internalRoles.includes(pr.author_association);

// --- 1. Check for a linked issue (external contributors only) ---
// Matches keywords: closes, fixes, resolves (and variants) followed by #<number>
const linkedIssuePattern =
/(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#\d+/i;
const hasLinkedIssue = isInternal || linkedIssuePattern.test(body);

// --- 2. Check that every commit is GPG/SSH-verified ---
const commits = await github.paginate(
github.rest.pulls.listCommits,
{
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
}
);

const unverifiedCommits = commits.filter(
(c) => !c.commit.verification?.verified
);
const allCommitsVerified = unverifiedCommits.length === 0;

// --- 3. Decide which labels / messages are needed ---
const labelsToAdd = [];
const failReasons = [];

if (!hasLinkedIssue) {
labelsToAdd.push('needs-discussion');
failReasons.push('no-linked-issue');
}

if (!allCommitsVerified) {
labelsToAdd.push('unverified-identity');
failReasons.push('unverified-commits');
}

if (labelsToAdd.length === 0) {
// All good — remove stale labels from previous runs if present
const existingLabels = (pr.labels || []).map((l) => l.name);
for (const label of ['needs-discussion', 'unverified-identity']) {
if (existingLabels.includes(label)) {
await github.rest.issues
.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
name: label,
})
.catch(() => {});
}
}
console.log('All PR requirements met — nothing to do.');
return;
}

// --- 4. Apply labels ---
// Ensure labels exist in the repo before adding them
for (const label of labelsToAdd) {
await github.rest.issues
.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label,
color: label === 'needs-discussion' ? 'e4e669' : 'd93f0b',
description:
label === 'needs-discussion'
? 'PR needs a linked issue before review'
: 'One or more commits are not GPG/SSH-verified',
})
.catch(() => {}); // label may already exist — that's fine

await github.rest.issues
.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: [label],
})
.catch(() => {});
}

// --- 5. Leave a single, supportive comment ---
let comment =
`Thanks so much for the PR! 🙏\n\n` +
`We're a small team following SOC 2 guidelines, so we need a couple of things ` +
`in place before we can start the review. Here's what we noticed:\n\n`;

if (failReasons.includes('no-linked-issue')) {
comment +=
`### No linked issue found\n` +
`We only review PRs that have been discussed in an issue first. ` +
`This helps us ensure every change is aligned with our roadmap and ` +
`avoids the frustration of writing code that might not fit the current architecture.\n\n` +
`Could you please link the issue where this was discussed? ` +
`Just add a line like the following to the PR description:\n\n` +
`\`\`\`\nFixes #<issue-number>\n\`\`\`\n\n` +
`Don't have an issue yet? [Open one here](https://github.com/${context.repo.owner}/${context.repo.repo}/issues/new/choose) ` +
`or start a conversation in the [Discussions tab](https://github.com/${context.repo.owner}/${context.repo.repo}/discussions). ` +
`We're happy to help shape the idea before you write any code!\n\n`;
}

if (failReasons.includes('unverified-commits')) {
comment +=
`### Unverified commit(s) detected\n` +
`We use signed commits to verify the identity of our contributors — ` +
`it's a great security best practice and part of our SOC 2 commitments!\n\n` +
`The following commits appear unverified:\n` +
unverifiedCommits
.map((c) => `- \`${c.sha.slice(0, 7)}\` ${c.commit.message.split('\n')[0]}`)
.join('\n') +
`\n\nTo sign future commits automatically, run:\n` +
`\`\`\`bash\ngit config --global commit.gpgsign true\n\`\`\`\n\n` +
`See [GitHub's guide on signing commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits) for full setup instructions.\n\n`;
}

comment +=
`We've paused the CI review until these are addressed. ` +
`Once you've made the updates, the checks will re-run automatically. ` +
`We're looking forward to collaborating with you! 💙`;

await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: comment,
});

// --- 6. Fail the status check to block merging ---
core.setFailed(
`PR requirements not met: ${failReasons.join(', ')}. ` +
`See the bot comment for details.`
);
68 changes: 68 additions & 0 deletions .github/workflows/welcome.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: Welcome First-Time Contributors

on:
pull_request:
types: [opened]

permissions:
pull-requests: write

jobs:
welcome:
name: Greet New Contributors
runs-on: ubuntu-latest
steps:
- name: Post welcome message for first-time contributors without a linked issue
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;

// author_association is NONE or FIRST_TIME_CONTRIBUTOR for newcomers
const isFirstTime = ['NONE', 'FIRST_TIME_CONTRIBUTOR'].includes(
pr.author_association
);

if (!isFirstTime) {
console.log('Not a first-time contributor — skipping welcome message.');
return;
}

const body = pr.body || '';
const linkedIssuePattern =
/(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#\d+/i;
const hasLinkedIssue = linkedIssuePattern.test(body);

if (hasLinkedIssue) {
console.log('Linked issue found — no welcome nudge needed.');
return;
}

const owner = context.repo.owner;
const repo = context.repo.repo;

const message =
`👋 Welcome to **Frappe UI React**, @${pr.user.login}! We're really glad you're here!\n\n` +
`It looks like this is your first contribution to the project — that's exciting, ` +
`and we want to make sure your effort pays off. Before we can review the PR, ` +
`we'd love to connect it to an issue so we can confirm the change fits our ` +
`current roadmap and architecture. This saves everyone time and avoids the ` +
`frustration of code being written that doesn't end up landing.\n\n` +
`Here's the path forward:\n\n` +
`1. **Open an issue** — Describe what you'd like to change and why:\n` +
` [Create an issue →](https://github.com/${owner}/${repo}/issues/new/choose)\n\n` +
`2. **Join the conversation** — Browse existing ideas and chat with maintainers:\n` +
` [Discussions tab →](https://github.com/${owner}/${repo}/discussions)\n\n` +
`3. **Link the issue here** — Once the issue is approved, add a line like this ` +
`to the PR description and the checks will re-run:\n` +
` \`\`\`\n Fixes #<issue-number>\n \`\`\`\n\n` +
`Our [CONTRIBUTING.md](https://github.com/${owner}/${repo}/blob/main/CONTRIBUTING.md) ` +
`has the full picture of how to get a PR merged successfully. ` +
`Don't hesitate to ask questions in the issue or here — we're happy to help. 💙`;

await github.rest.issues.createComment({
owner,
repo,
issue_number: pr.number,
body: message,
});
37 changes: 34 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ This guide will walk you through everything you need to know to get started.
## Summary

- [Code of conduct](#code-of-conduct)
- [Our commitment to a secure and reliable ecosystem](#our-commitment-to-a-secure-and-reliable-ecosystem)
- [A large spectrum of contributions](#a-large-spectrum-of-contributions)
- [Your first pull request](#your-first-pull-request)
- [Sending a pull request](#sending-a-pull-request)
- [How to get your PR merged successfully](#how-to-get-your-pr-merged-successfully)
- [The Issue-First rule](#the-issue-first-rule)
- [How to increase the chances of being accepted](#how-to-increase-the-chances-of-being-accepted)
- [CI checks and how to fix them](#ci-checks-and-how-to-fix-them)
- [Coding style](#coding-style)
Expand All @@ -22,6 +24,19 @@ This guide will walk you through everything you need to know to get started.
We have adopted the [Contributor Covenant](https://www.contributor-covenant.org/) as our code of conduct, and we expect project participants to adhere to it.
Please read [the full text](https://github.com/rtCamp/frappe-ui-react/blob/main/CODE_OF_CONDUCT.md) to understand what actions will and will not be tolerated.

## Our commitment to a secure and reliable ecosystem

We take the security and reliability of this library seriously — both for the maintainers and for every application that depends on it.
As part of that commitment, we follow [SOC 2](https://www.aicpa-cima.com/topic/audit-assurance/audit-and-assurance-advisory/system-and-organization-controls) guidelines in our development workflow.
In practice, this means a couple of things that affect how we handle contributions:

- **All commits must be signed.** We require [GPG or SSH commit signing](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits) so that we can cryptographically verify the identity of every contributor. This protects the supply chain and gives users confidence in every release.

- **External PRs must be linked to a pre-approved issue.** This applies to contributors who are not maintainers or org members. See [The Issue-First rule](#the-issue-first-rule) below.

These are not bureaucratic hurdles — they are guardrails that protect contributors, maintainers, and the broader community.
We have automated checks that will guide you through both requirements if anything is missing.

## A large spectrum of contributions

There are many ways to contribute to Frappe UI React, and writing code is only one part of the story—[documentation improvements](#contributing-to-the-documentation) can be just as important as code changes.
Expand All @@ -46,9 +61,25 @@ If nobody is working on it at the moment, please leave a comment stating that yo
If somebody claims an issue but doesn't follow up after more than a week, it's fine to take over, but you should still leave a comment.
If there has been no activity on the issue for 7 to 14 days, then it's safe to assume that nobody is working on it.

## Sending a pull request
## How to get your PR merged successfully

Frappe UI React is a community-driven project, and we genuinely love receiving contributions.
Following the steps below gives your PR the best possible chance of landing quickly and smoothly.

### The Issue-First rule

> **This rule applies to external contributors** — community members who are not org members or repository collaborators. Core maintainers are exempt.

**Before writing a single line of code, please open an issue.**

We know this can feel like an extra step, but it is one of the most valuable things we ask of external contributors.
Here is why: our architecture evolves continuously, and a change that seems straightforward from the outside might conflict with work already in progress, a planned refactor, or a deliberate design decision.
By discussing the idea in an issue first, maintainers can give you early feedback — saving you from investing hours (or days) into a PR that turns out to be a poor fit for the current codebase.
Think of it as a free code review before you write the code.

Frappe UI React is a community-driven project, so pull requests are always welcome, but before working on a large change, it's best to open an issue first to discuss it with the maintainers.
Once an issue is approved and you are ready to start, leave a comment on it so other contributors know it is being worked on.
Then, when you open your PR, link the issue using a [supported keyword](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) in the PR description (e.g. `Fixes #123`).
Our automated gatekeeper will check for this link and pause the review if it is missing.

When in doubt, keep your pull requests small.
For the best chances of being accepted, don't bundle more than one feature or bug fix per PR.
Expand Down
Loading