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
214 changes: 214 additions & 0 deletions .github/workflows/auto-label.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
name: Auto Label PR

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

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

jobs:
auto-label:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Auto Label PR
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { owner, repo, number } = context.issue;

// Get PR details
const pr = await github.rest.pulls.get({
owner,
repo,
pull_number: number
});

const title = pr.data.title.toLowerCase();
const body = pr.data.body ? pr.data.body.toLowerCase() : '';
const branch = pr.data.head.ref.toLowerCase();

// Get commits for additional context
const commits = await github.rest.pulls.listCommits({
owner,
repo,
pull_number: number
});

const commitMessages = commits.data
.map(commit => commit.commit.message.toLowerCase())
.join(' ');

const allText = `${title} ${body} ${branch} ${commitMessages}`;

// Define label rules
const labelRules = [
// Type labels
{
labels: ['🐛 bug'],
patterns: [/\b(bug|fix|bugfix|hotfix|patch|error|issue)\b/]
},
{
labels: ['✨ feature'],
patterns: [/\b(feat|feature|enhance)\b/]
},
{
labels: ['📚 docs'],
patterns: [/\b(doc|docs|documentation|readme|guide)\b/]
},
{
labels: ['🧹 chore'],
patterns: [/\b(chore|cleanup|refactor|style|format)\b/]
},
{
labels: ['🧪 test'],
patterns: [/\b(test|testing|spec|jest|coverage)\b/]
},
{
labels: ['🔧 ci/cd'],
patterns: [/\b(ci|cd|workflow|action|build|deploy|release)\b/]
},
{
labels: ['⚡ performance'],
patterns: [/\b(perf|performance|optimize|speed|fast)\b/]
},
{
labels: ['🔒 security'],
patterns: [/\b(security|vulnerability|auth|token|credential)\b/]
},

// Priority/Impact labels
{
labels: ['🚨 breaking change'],
patterns: [/\b(breaking|major|BREAKING CHANGE)\b/]
},
{
labels: ['🔥 critical'],
patterns: [/\b(critical|urgent|hotfix|emergency)\b/]
},
{
labels: ['📦 dependencies'],
patterns: [/\b(dep|deps|dependency|dependencies|package|npm|yarn)\b/]
},

// Component labels
{
labels: ['🎯 api'],
patterns: [/\b(api|endpoint|route|server|backend)\b/]
},
{
labels: ['🎨 ui/ux'],
patterns: [/\b(ui|ux|interface|frontend|design|style)\b/]
},
{
labels: ['🗄️ database'],
patterns: [/\b(db|database|sql|mongo|redis|migration)\b/]
},
{
labels: ['🔧 config'],
patterns: [/\b(config|configuration|settings|env|environment)\b/]
},

// Size labels based on file changes
{
labels: ['📝 small'],
condition: () => pr.data.additions + pr.data.deletions < 50
},
{
labels: ['📄 medium'],
condition: () => {
const total = pr.data.additions + pr.data.deletions;
return total >= 50 && total < 200;
}
},
{
labels: ['📚 large'],
condition: () => pr.data.additions + pr.data.deletions >= 200
}
];

// Collect labels to add
const labelsToAdd = new Set();

for (const rule of labelRules) {
let shouldAddLabel = false;

// Check patterns
if (rule.patterns) {
shouldAddLabel = rule.patterns.some(pattern => pattern.test(allText));
}

// Check custom conditions
if (rule.condition) {
shouldAddLabel = rule.condition();
}

if (shouldAddLabel) {
rule.labels.forEach(label => labelsToAdd.add(label));
}
}

// Special handling for conventional commits
const conventionalPatterns = {
'feat': ['✨ feature'],
'fix': ['🐛 bug'],
'docs': ['📚 docs'],
'style': ['🧹 chore'],
'refactor': ['🧹 chore'],
'perf': ['⚡ performance'],
'test': ['🧪 test'],
'chore': ['🧹 chore'],
'ci': ['🔧 ci/cd'],
'build': ['🔧 ci/cd']
};

for (const [prefix, labels] of Object.entries(conventionalPatterns)) {
const pattern = new RegExp(`\\b${prefix}(\\([^)]+\\))?:\\s`, 'i');
if (pattern.test(allText)) {
labels.forEach(label => labelsToAdd.add(label));
}
}

// Get current labels
const currentLabels = await github.rest.issues.listLabelsOnIssue({
owner,
repo,
issue_number: number
});

const currentLabelNames = new Set(currentLabels.data.map(label => label.name));

// Only add labels that don't already exist
const newLabels = Array.from(labelsToAdd).filter(label => !currentLabelNames.has(label));

if (newLabels.length > 0) {
console.log(`Adding labels: ${newLabels.join(', ')}`);

await github.rest.issues.addLabels({
owner,
repo,
issue_number: number,
labels: newLabels
});

// Comment on the PR about auto-labeling
const labelList = newLabels.map(label => `\`${label}\``).join(', ');
await github.rest.issues.createComment({
owner,
repo,
issue_number: number,
body: `🤖 **Auto-labeled this PR with:** ${labelList}\n\n_This was done automatically based on the PR title, description, and commits._`
});
} else {
console.log('No new labels to add');
}
6 changes: 6 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,18 @@ jobs:
release:
runs-on: ubuntu-latest
if: ${{ !contains(github.event.head_commit.message, 'skip ci') }}
permissions:
contents: write
issues: write
pull-requests: write
id-token: write

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
persist-credentials: true

- name: Use Node.js 22
uses: actions/setup-node@v4
Expand Down
106 changes: 106 additions & 0 deletions .github/workflows/setup-labels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: Setup Repository Labels

on:
workflow_dispatch: # Manual trigger
push:
branches:
- main
paths:
- '.github/workflows/setup-labels.yml'

permissions:
issues: write

jobs:
setup-labels:
runs-on: ubuntu-latest
steps:
- name: Setup Labels
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const { owner, repo } = context.repo;

// Define all labels with colors
const labels = [
// Type labels
{ name: '🐛 bug', color: 'd73a4a', description: 'Something isn\'t working' },
{ name: '✨ feature', color: 'a2eeef', description: 'New feature or request' },
{ name: '📚 docs', color: '0075ca', description: 'Improvements or additions to documentation' },
{ name: '🧹 chore', color: 'fef2c0', description: 'Maintenance tasks' },
{ name: '🧪 test', color: '1d76db', description: 'Testing related' },
{ name: '🔧 ci/cd', color: '0052cc', description: 'CI/CD and build system' },
{ name: '⚡ performance', color: 'fbca04', description: 'Performance improvements' },
{ name: '🔒 security', color: 'b60205', description: 'Security related' },

// Priority/Impact labels
{ name: '🚨 breaking change', color: 'b60205', description: 'Breaking changes' },
{ name: '🔥 critical', color: 'b60205', description: 'Critical priority' },
{ name: '📦 dependencies', color: '0366d6', description: 'Dependencies updates' },

// Component labels
{ name: '🎯 api', color: '7057ff', description: 'API related' },
{ name: '🎨 ui/ux', color: 'e99695', description: 'UI/UX related' },
{ name: '🗄️ database', color: '1d76db', description: 'Database related' },
{ name: '🔧 config', color: 'fbca04', description: 'Configuration related' },

// Size labels
{ name: '📝 small', color: 'c2e0c6', description: 'Small change (< 50 lines)' },
{ name: '📄 medium', color: 'f9d0c4', description: 'Medium change (50-200 lines)' },
{ name: '📚 large', color: 'f85149', description: 'Large change (> 200 lines)' },

// Status labels
{ name: '👀 needs review', color: 'fbca04', description: 'Needs review' },
{ name: '✅ ready to merge', color: '0e8a16', description: 'Ready to merge' },
{ name: '🚧 work in progress', color: 'ff6b6b', description: 'Work in progress' },
{ name: '❌ needs changes', color: 'd93f0b', description: 'Needs changes' },
{ name: '🔄 review requested', color: 'fbca04', description: 'Review requested' },

// Special labels
{ name: 'good first issue', color: '7057ff', description: 'Good for newcomers' },
{ name: 'help wanted', color: '006b75', description: 'Extra attention is needed' },
{ name: 'question', color: 'd876e3', description: 'Further information is requested' },
{ name: 'wontfix', color: 'ffffff', description: 'This will not be worked on' },
{ name: 'duplicate', color: 'cfd3d7', description: 'This issue or pull request already exists' },
{ name: 'invalid', color: 'e4e669', description: 'This doesn\'t seem right' }
];

// Get existing labels
const existingLabels = await github.rest.issues.listLabelsForRepo({
owner,
repo
});

const existingLabelNames = new Set(existingLabels.data.map(label => label.name));

// Create or update labels
for (const label of labels) {
try {
if (existingLabelNames.has(label.name)) {
// Update existing label
await github.rest.issues.updateLabel({
owner,
repo,
name: label.name,
color: label.color,
description: label.description
});
console.log(`Updated label: ${label.name}`);
} else {
// Create new label
await github.rest.issues.createLabel({
owner,
repo,
name: label.name,
color: label.color,
description: label.description
});
console.log(`Created label: ${label.name}`);
}
} catch (error) {
console.error(`Error processing label ${label.name}:`, error.message);
}
}

console.log('Label setup completed!');
18 changes: 18 additions & 0 deletions .releaserc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"branches": ["main"],
"repositoryUrl": "https://github.com/xwartz/cursor-api.git",
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"@semantic-release/npm",
"@semantic-release/github",
[
"@semantic-release/git",
{
"assets": ["package.json", "CHANGELOG.md"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
]
]
}
Loading