Skip to content

fix: prevent TypeError when creating new project from Planet interface #349

fix: prevent TypeError when creating new project from Planet interface

fix: prevent TypeError when creating new project from Planet interface #349

name: PR Category Check
# Ensures every PR has at least one category checkbox checked.
# Automatically applies matching GitHub labels to the PR.
# Creates labels with custom colors if they don't exist yet.
# Uses pull_request_target so it works for fork PRs too (runs in base repo context).
# This is safe because we only read the PR body from the event payload — no fork code is checked out or executed.
on:
pull_request_target:
types: [opened, edited, synchronize, reopened]
permissions:
pull-requests: write
contents: read
jobs:
check-pr-category:
name: Validate PR Category & Auto-Label
runs-on: ubuntu-latest
steps:
- name: Check categories and apply labels
uses: actions/github-script@v7
with:
script: |
const prBody = context.payload.pull_request.body || '';
const prNumber = context.payload.pull_request.number;
// Category checkboxes mapped to their GitHub label names, colors, and descriptions
const categories = [
{
label: 'bug fix',
color: 'D73A4A',
description: 'Fixes a bug or incorrect behavior',
displayName: 'Bug Fix',
pattern: /-\s*\[x\]\s*Bug Fix/i,
},
{
label: 'feature',
color: '9333EA',
description: 'Adds new functionality',
displayName: 'Feature',
pattern: /-\s*\[x\]\s*Feature/i,
},
{
label: 'performance',
color: 'F97316',
description: 'Improves performance (load time, memory, rendering)',
displayName: 'Performance',
pattern: /-\s*\[x\]\s*Performance/i,
},
{
label: 'tests',
color: '3B82F6',
description: 'Adds or updates test coverage',
displayName: 'Tests',
pattern: /-\s*\[x\]\s*Tests/i,
},
{
label: 'documentation',
color: '10B981',
description: 'Updates to docs, comments, or README',
displayName: 'Documentation',
pattern: /-\s*\[x\]\s*Documentation/i,
},
];
const checkedCategories = categories.filter(cat => cat.pattern.test(prBody));
const uncheckedCategories = categories.filter(cat => !cat.pattern.test(prBody));
// --- Step 1: Fail CI if no category is selected ---
if (checkedCategories.length === 0) {
const message = [
'## PR Category Required',
'',
'This pull request does not have any **PR Category** selected.',
'Please edit your PR description and check **at least one** category checkbox:',
'',
'| Category | Description |',
'|----------|-------------|',
'| Bug Fix | Fixes a bug or incorrect behavior |',
'| Feature | Adds new functionality |',
'| Performance | Improves performance |',
'| Tests | Adds or updates test coverage |',
'| Documentation | Updates to docs, comments, or README |',
'',
'Example: Change `- [ ] Bug Fix` to `- [x] Bug Fix`',
'',
'> **Tip:** You can select multiple categories if your PR spans several areas.',
].join('\n');
core.setFailed(message);
return;
}
// --- Step 2: Ensure labels exist with proper colors ---
const { data: existingLabels } = await github.rest.issues.listLabelsForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
per_page: 100,
});
const existingLabelNames = existingLabels.map(l => l.name);
for (const cat of categories) {
if (!existingLabelNames.includes(cat.label)) {
try {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: cat.label,
color: cat.color,
description: cat.description,
});
core.info(`Created label: "${cat.label}" with color #${cat.color}`);
} catch (error) {
core.warning(`Could not create label "${cat.label}". Error: ${error.message}`);
}
}
}
// --- Step 3: Auto-apply labels for checked categories ---
const labelsToAdd = checkedCategories.map(cat => cat.label);
const labelsToRemove = uncheckedCategories.map(cat => cat.label);
// Get current labels on the PR
const { data: currentLabels } = await github.rest.issues.listLabelsOnIssue({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
});
const currentLabelNames = currentLabels.map(l => l.name);
// Add labels that are checked but not yet on the PR
for (const label of labelsToAdd) {
if (!currentLabelNames.includes(label)) {
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
labels: [label],
});
core.info(`Added label: "${label}"`);
} catch (error) {
core.warning(`Could not add label "${label}". Error: ${error.message}`);
}
}
}
// Remove labels that are unchecked but still on the PR
for (const label of labelsToRemove) {
if (currentLabelNames.includes(label)) {
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
name: label,
});
core.info(`Removed label: "${label}"`);
} catch (error) {
core.warning(`Could not remove label "${label}". Error: ${error.message}`);
}
}
}
const selected = checkedCategories.map(c => c.displayName).join(', ');
core.info(`PR categories selected: ${selected}`);
core.info(`Labels synced: ${labelsToAdd.join(', ')}`);