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
166 changes: 166 additions & 0 deletions .github/workflows/pr-size.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
name: PR Size

on:
pull_request_target:
types: [opened, reopened, synchronize, ready_for_review, converted_to_draft]

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

jobs:
label:
name: Label PR size
runs-on: ubuntu-24.04
concurrency:
group: pr-size-${{ github.event.pull_request.number }}
cancel-in-progress: true
steps:
- name: Sync PR size label
uses: actions/github-script@v7
with:
script: |
const issueNumber = context.payload.pull_request.number;
const additions = context.payload.pull_request.additions ?? 0;
const deletions = context.payload.pull_request.deletions ?? 0;
const changedLines = additions + deletions;

const managedLabels = [
{
name: "size:XS",
color: "0e8a16",
description: "0-9 changed lines (additions + deletions).",
},
{
name: "size:S",
color: "5ebd3e",
description: "10-29 changed lines (additions + deletions).",
},
{
name: "size:M",
color: "fbca04",
description: "30-99 changed lines (additions + deletions).",
},
{
name: "size:L",
color: "fe7d37",
description: "100-499 changed lines (additions + deletions).",
},
{
name: "size:XL",
color: "d93f0b",
description: "500-999 changed lines (additions + deletions).",
},
{
name: "size:XXL",
color: "b60205",
description: "1,000+ changed lines (additions + deletions).",
},
];

const managedLabelNames = new Set(managedLabels.map((label) => label.name));

const resolveSizeLabel = (totalChangedLines) => {
if (totalChangedLines < 10) {
return "size:XS";
}

if (totalChangedLines < 30) {
return "size:S";
}

if (totalChangedLines < 100) {
return "size:M";
}

if (totalChangedLines < 500) {
return "size:L";
}

if (totalChangedLines < 1000) {
return "size:XL";
}

return "size:XXL";
};

for (const label of managedLabels) {
try {
const { data: existing } = await github.rest.issues.getLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label.name,
});

if (
existing.color !== label.color ||
(existing.description ?? "") !== label.description
) {
await github.rest.issues.updateLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label.name,
color: label.color,
description: label.description,
});
}
} catch (error) {
if (error.status !== 404) {
throw error;
}

try {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label.name,
color: label.color,
description: label.description,
});
} catch (createError) {
if (createError.status !== 422) {
throw createError;
}
}
}
}

const nextLabelName = resolveSizeLabel(changedLines);

const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
per_page: 100,
});

for (const label of currentLabels) {
if (!managedLabelNames.has(label.name) || label.name === nextLabelName) {
continue;
}

try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
name: label.name,
});
} catch (removeError) {
if (removeError.status !== 404) {
throw removeError;
}
}
}

if (!currentLabels.some((label) => label.name === nextLabelName)) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
labels: [nextLabelName],
});
}

core.info(`PR #${issueNumber}: ${changedLines} changed lines -> ${nextLabelName}`);
58 changes: 41 additions & 17 deletions .github/workflows/pr-vouch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ jobs:
},
];

const managedLabelNames = managedLabels.map((label) => label.name);
const managedLabelNames = new Set(managedLabels.map((label) => label.name));

for (const label of managedLabels) {
try {
Expand All @@ -138,13 +138,19 @@ jobs:
throw error;
}

await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label.name,
color: label.color,
description: label.description,
});
try {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: label.name,
color: label.color,
description: label.description,
});
} catch (createError) {
if (createError.status !== 422) {
throw createError;
}
}
}
}

Expand All @@ -159,17 +165,35 @@ jobs:
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
per_page: 100,
});

const preservedLabels = currentLabels
.map((label) => label.name)
.filter((name) => !managedLabelNames.includes(name));
for (const label of currentLabels) {
if (!managedLabelNames.has(label.name) || label.name === nextLabelName) {
continue;
}

await github.rest.issues.setLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
labels: [...preservedLabels, nextLabelName],
});
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
name: label.name,
});
} catch (removeError) {
if (removeError.status !== 404) {
throw removeError;
}
}
}

if (!currentLabels.some((label) => label.name === nextLabelName)) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
labels: [nextLabelName],
});
}

core.info(`PR #${issueNumber}: ${status} -> ${nextLabelName}`);
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ You can still open an issue or PR, but please do so knowing there is a high chan

If that sounds annoying, that is because it is. This project is still early and we are trying to keep scope, quality, and direction under control.

PRs are automatically labeled with a `vouch:*` trust status.
PRs are automatically labeled with a `vouch:*` trust status and a `size:*` diff size based on changed lines.

If you are an external contributor, expect `vouch:unvouched` until we explicitly add you to [.github/VOUCHED.td](.github/VOUCHED.td).

Expand Down
Loading