chore: import upstream snapshot with attribution
CD - Docker - GHCR Images / Build and Push Images (push) Waiting to run
i18n - Build Validation / Validate i18n Builds (24) (push) Has been cancelled
CI - Node.js / Lint (24) (push) Has been cancelled
CI - Node.js / Build (24) (push) Has been cancelled
CI - Node.js / Test (24) (push) Has been cancelled
CI - Node.js / Test - Upcoming Changes (24) (push) Has been cancelled
CI - Node.js / Test - i18n (italian, 24) (push) Has been cancelled
CI - Node.js / Test - i18n (portuguese, 24) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 11:55:53 +08:00
commit dde272c4b8
19405 changed files with 2730632 additions and 0 deletions
@@ -0,0 +1,24 @@
'use strict';
module.exports = async ({ github, context, core }) => {
const prAuthor = context.payload.pull_request.user.login;
const teamSlugs = ['dev-team', 'curriculum', 'staff', 'moderators'];
const membershipChecks = teamSlugs.map(team_slug =>
github.rest.teams
.getMembershipForUserInOrg({
org: 'freeCodeCamp',
team_slug,
username: prAuthor
})
.then(({ data }) => data.state === 'active')
.catch(() => false)
);
const results = await Promise.all(membershipChecks);
const isOrgTeamMember = results.some(Boolean);
const isAllowListed =
isOrgTeamMember || ['camperbot', 'renovate[bot]'].includes(prAuthor);
core.setOutput('is_allow_listed', isAllowListed);
};
@@ -0,0 +1,84 @@
'use strict';
async function addDeprioritizedLabel(github, context) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
labels: ['deprioritized']
});
}
module.exports = async ({ github, context, core, isAllowListed }) => {
if (isAllowListed === 'true') return;
const result = await github.graphql(
`query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
closingIssuesReferences(first: 10) {
nodes {
number
labels(first: 10) { nodes { name } }
assignees(first: 10) { nodes { login } }
}
}
}
}
}`,
{
owner: context.repo.owner,
repo: context.repo.repo,
number: context.payload.pull_request.number
}
);
const pr = result.repository?.pullRequest;
if (!pr) return;
const linkedIssues = pr.closingIssuesReferences.nodes;
if (linkedIssues.length === 0) {
core.setOutput('failure_reason', 'no_linked_issue');
core.setFailed('No linked issue found.');
await addDeprioritizedLabel(github, context);
return;
}
const hasWaitingTriage = linkedIssues.some(issue =>
issue.labels.nodes.some(l => l.name === 'status: waiting triage')
);
if (hasWaitingTriage) {
core.setOutput('failure_reason', 'waiting_triage');
core.setFailed('Linked issue has not been triaged yet.');
await addDeprioritizedLabel(github, context);
return;
}
const prAuthor = context.payload.pull_request.user.login;
const isNaomiSprintAssignee = linkedIssues.some(
issue =>
issue.labels.nodes.some(l => l.name === "Naomi's Sprints") &&
issue.assignees.nodes.some(a => a.login === prAuthor)
);
if (isNaomiSprintAssignee) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
labels: ["Naomi's Sprints"]
});
return;
}
const isOpenForContribution = linkedIssues.some(issue =>
issue.labels.nodes.some(
l => l.name === 'help wanted' || l.name === 'first timers only'
)
);
if (!isOpenForContribution) {
core.setOutput('failure_reason', 'not_open_for_contribution');
core.setFailed('Linked issue is not open for contribution.');
await addDeprioritizedLabel(github, context);
}
};
@@ -0,0 +1,39 @@
'use strict';
module.exports = async ({ github, context, core, isAllowListed }) => {
if (isAllowListed === 'true') return;
const body = (context.payload.pull_request.body || '').toLowerCase();
// The template must be present and the first 3 checkboxes must be
// ticked. The last checkbox (tested locally) is acceptable to leave
// unticked.
const templatePresent = body.includes('checklist:');
const requiredTicked = [
'i have read and followed the contribution guidelines',
'i have read and followed the how to open a pull request guide',
'my pull request targets the'
];
// Strip markdown links ([text](url) → text) before matching so contributors
// who omit the link syntax (e.g. type plain text) still pass the check.
const normalizedBody = body
.replace(/\[\s*x\s*\]/g, '[x]')
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
const allRequiredTicked = requiredTicked.every(item =>
normalizedBody.includes(`[x] ${item}`)
);
if (templatePresent && allRequiredTicked) return;
core.setOutput('failure_reason', 'incomplete_checklist');
core.setFailed(
'PR description is missing the required checklist or some items are incomplete.'
);
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
labels: ['deprioritized']
});
};
@@ -0,0 +1,79 @@
'use strict';
// Returns the minimum number of single-character edits (insert, delete, substitute)
// needed to turn string `a` into string `b`.
function levenshtein(a, b) {
const dp = Array.from({ length: a.length + 1 }, (_, i) =>
Array.from({ length: b.length + 1 }, (_, j) =>
i === 0 ? j : j === 0 ? i : 0
)
);
for (let i = 1; i <= a.length; i++) {
for (let j = 1; j <= b.length; j++) {
dp[i][j] =
a[i - 1] === b[j - 1]
? dp[i - 1][j - 1]
: 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
}
}
return dp[a.length][b.length];
}
module.exports = async ({ github, context }) => {
const title = context.payload.pull_request.title;
const ccRegex =
/^(feat|fix|refactor|docs|chore|build|ci|test|perf|revert)(\([^)]+\))?: .+/;
if (ccRegex.test(title)) return;
const types = [
'feat',
'fix',
'refactor',
'docs',
'chore',
'build',
'ci',
'test',
'perf',
'revert'
];
let newTitle = title;
// Fix 1: space between type and scope — "feat (scope):" → "feat(scope):"
newTitle = newTitle.replace(/^(\w+)\s+(\([^)]+\):)/, '$1$2');
// Fix 2: missing colon after scope — "feat(scope) desc" → "feat(scope): desc"
newTitle = newTitle.replace(/^(\w+\([^)]+\)) ([^:])/, '$1: $2');
// Fix 3: typo in type — "refator(scope):" → "refactor(scope):" (distance ≤ 2)
const typoMatch = newTitle.match(/^(\w+)(\([^)]+\))?:/);
if (typoMatch) {
const candidate = typoMatch[1];
if (!types.includes(candidate)) {
const closest = types
.map(t => ({ t, d: levenshtein(candidate, t) }))
.filter(x => x.d <= 2)
.sort((a, b) => a.d - b.d)[0];
if (closest) newTitle = newTitle.replace(candidate, closest.t);
}
}
// Fix 4: missing space after colon — "fix:desc" → "fix: desc"
newTitle = newTitle.replace(/^(\w+(?:\([^)]+\))?):(\S)/, '$1: $2');
// Catch-all: prefix with "fix: " if still not a valid CC title
if (!ccRegex.test(newTitle)) {
newTitle = `fix: ${newTitle}`;
}
if (newTitle !== title) {
await github.rest.pulls.update({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.pull_request.number,
title: newTitle
});
}
};
@@ -0,0 +1,107 @@
'use strict';
const FOOTER =
'\n\n---\nJoin us in our [chat room](https://discord.gg/PRyKn3Vbay) or our [forum](https://forum.freecodecamp.org/c/contributors/3) if you have any questions or need help with contributing.';
const TEMPLATE_BLOCK = [
'```md',
'Checklist:',
'',
'<!-- Please follow this checklist and put an x in each of the boxes, like this: [x]. It will ensure that our team takes your pull request seriously. -->',
'',
'- [ ] I have read and followed the [contribution guidelines](https://contribute.freecodecamp.org).',
'- [ ] I have read and followed the [how to open a pull request guide](https://contribute.freecodecamp.org/how-to-open-a-pull-request/).',
"- [ ] My pull request targets the `main` branch of freeCodeCamp.",
'- [ ] I have tested these changes either locally on my machine, or GitHub Codespaces.',
'',
'<!--If your pull request closes a GitHub issue, replace the XXXXX below with the issue number.-->',
'',
'Closes #XXXXX',
'',
'<!-- Feel free to add any additional description of changes below this line -->',
'```'
].join('\n');
const MESSAGES = {
incomplete_checklist: [
'**Checklist:** The PR description is missing the required checklist or some of its items are not completed:',
'',
'1. The `Checklist:` heading is present in the PR description.',
'2. The checkbox items are ticked (changed from `[ ]` to `[x]`).',
'3. You have actually completed the items in the checklist.',
'',
'Please edit your PR description to include the following template with the checklist items completed.',
'',
TEMPLATE_BLOCK
].join('\n'),
no_linked_issue: [
'**Linked Issue:** We kindly ask that contributors open an issue before submitting a PR so the change can be discussed and approved before work begins. This helps avoid situations where significant effort goes into something we ultimately cannot merge.',
'',
'Please open an issue first and allow it to be triaged. Once the issue is open for contribution, you are welcome to update this pull request to reflect the issue consensus. Until then, we will not be able to review your pull request.'
].join('\n'),
waiting_triage: [
'**Linked Issue:** The linked issue has not been triaged yet, and a solution has not been agreed upon. Once the issue is open for contribution, you are welcome to update this pull request to reflect the issue consensus. Until then, we will not be able to review your pull request.'
].join('\n'),
not_open_for_contribution:
'**Linked Issue:** The linked issue is not open for contribution. If you are looking for issues to contribute to, please check out issues labeled [`help wanted`](https://github.com/freeCodeCamp/freeCodeCamp/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) or [`first timers only`](https://github.com/freeCodeCamp/freeCodeCamp/issues?q=is%3Aissue+is%3Aopen+label%3A%22first+timers+only%22).'
};
module.exports = async ({
github,
context,
templateResult,
templateReason,
linkedIssueResult,
linkedIssueReason
}) => {
const allPassed =
templateResult === 'success' && linkedIssueResult === 'success';
if (allPassed) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
name: 'deprioritized'
});
} catch {
// Label may not exist — ignore.
}
return;
}
// On edit, don't re-comment — the original comment is already there.
if (context.payload.action === 'edited') return;
const sections = [];
if (templateResult === 'failure' && MESSAGES[templateReason]) {
sections.push(MESSAGES[templateReason]);
}
if (linkedIssueResult === 'failure' && MESSAGES[linkedIssueReason]) {
sections.push(MESSAGES[linkedIssueReason]);
}
if (sections.length === 0) return;
const body =
[
'Hi there,',
'',
'Thanks for opening this pull request.',
'',
'The automated checks found some issues:',
'',
sections.join('\n\n')
].join('\n') + FOOTER;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body
});
};