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
17 changes: 17 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
groups:
dev-dependencies:
dependency-type: "development"
update-types:
- "minor"
- "patch"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
34 changes: 34 additions & 0 deletions .github/workflows/conventional-commit-title.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Conventional Commit Title

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

permissions:
pull-requests: read

jobs:
check-title:
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
types: |
feat
fix
chore
docs
refactor
test
perf
style
ci
build
revert
requireScope: false
subjectPattern: ^[A-Za-z].+$
subjectPatternError: |
The PR subject (after the colon) must start with a letter and be non-empty.
Example: "feat(#2345): add bulk export for tenders"
61 changes: 61 additions & 0 deletions .github/workflows/dependabot-auto-merge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Dependabot auto-merge

# Auto-approves and enables GitHub's auto-merge for Dependabot PRs when they are safe:
# - patch-level updates (any dependency)
# - minor-level updates for devDependencies only
# Major updates and minor updates on runtime deps are left for human review.
#
# Auto-merge waits for branch-protection required checks to pass before merging.
# Without branch protection, auto-merge falls through to immediate merge.

on:
pull_request:
branches: [main]

permissions:
contents: write
pull-requests: write

jobs:
auto-merge:
if: github.actor == 'dependabot[bot]'
runs-on: ubuntu-latest
steps:
- name: Fetch Dependabot metadata
id: metadata
uses: dependabot/fetch-metadata@v3
with:
github-token: "${{ secrets.GITHUB_TOKEN }}"

- name: Approve + enable auto-merge (patch)
if: steps.metadata.outputs.update-type == 'version-update:semver-patch'
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr review --approve "$PR_URL" --body "Auto-approving patch-level dependency update."
gh pr merge --auto --squash "$PR_URL"

- name: Approve + enable auto-merge (minor devDependency)
if: >
steps.metadata.outputs.update-type == 'version-update:semver-minor'
&& steps.metadata.outputs.dependency-type == 'direct:development'
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh pr review --approve "$PR_URL" --body "Auto-approving minor dev-dependency update."
gh pr merge --auto --squash "$PR_URL"

- name: Flag for human review (otherwise)
if: >
steps.metadata.outputs.update-type == 'version-update:semver-major'
|| (steps.metadata.outputs.update-type == 'version-update:semver-minor'
&& steps.metadata.outputs.dependency-type != 'direct:development')
env:
PR_URL: ${{ github.event.pull_request.html_url }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
UPDATE_TYPE: ${{ steps.metadata.outputs.update-type }}
DEP_TYPE: ${{ steps.metadata.outputs.dependency-type }}
run: |
gh pr comment "$PR_URL" --body "⚠️ Not auto-merging: \`$UPDATE_TYPE\` on \`$DEP_TYPE\`. Requires human review."
52 changes: 52 additions & 0 deletions .github/workflows/issue-validator.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Issue Validator

on:
issues:
types: [opened, reopened, edited]

permissions:
issues: write

jobs:
validate:
runs-on: ubuntu-latest
if: github.event.action == 'opened' || github.event.action == 'reopened'
steps:
- name: Ensure type label or request one
uses: actions/github-script@v7
with:
script: |
const issue = context.payload.issue;
const labels = issue.labels.map(l => l.name);
const hasTypeLabel = labels.some(name => name.startsWith('type:'));

if (hasTypeLabel) {
core.info(`Has type label - OK`);
return;
}

// No type label: user created via API or stripped it. Add needs-triage, comment.
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: ['needs-triage']
});

await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: [
'👋 This issue is missing a `type:*` label.',
'',
'Please use one of the issue templates when opening new issues:',
'- 🐛 [Bug](../../issues/new?template=bug.yml)',
'- ✨ [Feature](../../issues/new?template=feature.yml)',
'- 🔧 [Chore](../../issues/new?template=chore.yml)',
'',
'Or add the appropriate `type:bug` / `type:feature` / `type:chore` / `type:spike` label manually.',
'',
'_Auto-labeled `needs-triage`; @PetarStoev02 will review._'
].join('\n')
});
41 changes: 41 additions & 0 deletions .github/workflows/pr-issue-link.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: PR Issue Link

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

jobs:
check-issue-link:
runs-on: ubuntu-latest
steps:
- name: Check for issue reference
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const title = pr.title || '';
const body = pr.body || '';

// Exempt chore(*) and docs(*) scopes (legitimate no-issue PRs)
const exemptScopes = /^(chore|docs)(\([^)]+\))?!?: /;
if (exemptScopes.test(title)) {
core.info(`Exempt scope detected in title: "${title}"`);
return;
}

// Accept any of: "Closes #N", "Fixes #N", "Refs #N" in body, or "(#N)" scope in title
const closesPattern = /(?:closes|fixes|resolves|refs|references)\s+#\d+/i;
const scopePattern = /\(#\d+\)/;

const hasBodyRef = closesPattern.test(body);
const hasTitleRef = scopePattern.test(title);

if (!hasBodyRef && !hasTitleRef) {
core.setFailed(
'PR must reference an issue. Add "Closes #N" / "Fixes #N" / "Refs #N" to the PR body, ' +
'OR include an issue scope like "fix(#1234): ..." in the PR title. ' +
'Exception: chore(*) and docs(*) PRs are exempt.'
);
} else {
core.info(`Issue link found: body=${hasBodyRef}, title=${hasTitleRef}`);
}
38 changes: 38 additions & 0 deletions .github/workflows/stale.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Stale Issues

on:
schedule:
- cron: '0 2 * * *' # Daily 02:00 UTC
workflow_dispatch:

permissions:
issues: write

jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10
with:
days-before-issue-stale: 60
days-before-issue-close: 30
stale-issue-label: 'stale'
stale-issue-message: |
This issue has had no activity in 60 days. Flagging `stale`.
If it's still relevant, comment or add it to a due-dated milestone within 30 days, otherwise it will be auto-closed.
close-issue-message: |
Auto-closing after 90 days of inactivity. Reopen with fresh details if still relevant.
close-issue-reason: 'not_planned'

# Exempt: any issue with a due-dated milestone, blocked label, or assignee
exempt-issue-labels: 'blocked,non-actionable'
exempt-all-milestones: false
exempt-milestones: ''
exempt-assignees: '' # Issues with an assignee are not exempt; stale still applies if no activity

# Never touch PRs
days-before-pr-stale: -1
days-before-pr-close: -1

operations-per-run: 100
ascending: true
Loading