mirror of
https://github.com/cozystack/cozystack.git
synced 2026-10-29 08:07:33 +09:00
The labeler falls back to area/uncategorized for a scope it does not know, and re-applies the fallback on every run, so a manually fixed label does not stay. The local-storage package is a storage component, and its PR title scope should land on area/storage. Assisted-by: LLM Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
299 lines
13 KiB
YAML
299 lines
13 KiB
YAML
name: PR Auto-Label
|
|
|
|
on:
|
|
pull_request_target:
|
|
# Note: 'edited' is intentionally omitted. It fires on every PR body edit,
|
|
# which causes long-lived bot PRs (e.g. Renovate) to re-run this job dozens
|
|
# of times on an unchanged commit. Labels are re-applied on the next push
|
|
# (synchronize) or reopen instead.
|
|
types: [opened, reopened, synchronize]
|
|
|
|
# Top-level token defaults to read-only. A job-level block replaces this one
|
|
# rather than adding to it, so each job restates every scope it needs.
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
label:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
pull-requests: write
|
|
steps:
|
|
- name: Apply labels from PR title
|
|
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
|
|
with:
|
|
script: |
|
|
// Conventional Commits types accepted by Cozystack (per docs/agents/contributing.md):
|
|
// feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
|
|
// Mapping below maps a subset to kind/* — types not listed do not produce a kind/*.
|
|
const typeToKind = {
|
|
feat: 'kind/feature',
|
|
fix: 'kind/bug',
|
|
docs: 'kind/documentation',
|
|
chore: 'kind/cleanup',
|
|
refactor: 'kind/cleanup',
|
|
// style, perf, test, build, ci, revert — no kind mapping
|
|
};
|
|
|
|
// scope -> area/* mapping. Keys are the scopes observed in cozystack issues
|
|
// and PRs. Add new entries when a scope recurs (3+ times).
|
|
const scopeToArea = {
|
|
// area/api
|
|
'api': 'area/api',
|
|
'cozystack-api': 'area/api',
|
|
'registry': 'area/api',
|
|
|
|
// area/ai
|
|
'agents': 'area/ai',
|
|
'ai': 'area/ai',
|
|
|
|
// area/build
|
|
'build': 'area/build',
|
|
|
|
// area/ci
|
|
'ci': 'area/ci',
|
|
'workflows': 'area/ci',
|
|
'labeler': 'area/ci',
|
|
|
|
// area/dependencies — Renovate uses scope `deps` (see .github/renovate.json)
|
|
'deps': 'area/dependencies',
|
|
'dependencies': 'area/dependencies',
|
|
'renovate': 'area/dependencies',
|
|
|
|
// area/dx — developer tooling: hack/, package Makefiles, cozypkg
|
|
'dx': 'area/dx',
|
|
'hack': 'area/dx',
|
|
'cozypkg': 'area/dx',
|
|
|
|
// area/dashboard
|
|
'dashboard': 'area/dashboard',
|
|
'cozystack-ui': 'area/dashboard',
|
|
'console': 'area/dashboard',
|
|
'dashboard-controller': 'area/dashboard',
|
|
|
|
// area/database
|
|
'postgres': 'area/database',
|
|
'postgres-operator': 'area/database',
|
|
'mariadb': 'area/database',
|
|
'mariadb-operator': 'area/database',
|
|
'redis': 'area/database',
|
|
'valkey': 'area/database',
|
|
'etcd': 'area/database',
|
|
'kafka': 'area/database',
|
|
'clickhouse': 'area/database',
|
|
'mongodb': 'area/database',
|
|
'rabbitmq': 'area/database',
|
|
'nats': 'area/database',
|
|
'qdrant': 'area/database',
|
|
'foundationdb': 'area/database',
|
|
'database': 'area/database',
|
|
'etcd-operator': 'area/database',
|
|
'opensearch': 'area/database',
|
|
'opensearch-operator': 'area/database',
|
|
|
|
// area/extra
|
|
'extra': 'area/extra',
|
|
|
|
// area/kamaji
|
|
'kamaji': 'area/kamaji',
|
|
|
|
// area/keycloak
|
|
'keycloak': 'area/keycloak',
|
|
'keycloak-configure': 'area/keycloak',
|
|
|
|
// area/kubernetes
|
|
'kubernetes': 'area/kubernetes',
|
|
'apps/kubernetes': 'area/kubernetes',
|
|
'kubernetes-nodes': 'area/kubernetes',
|
|
|
|
// area/monitoring
|
|
'monitoring': 'area/monitoring',
|
|
'metrics-server': 'area/monitoring',
|
|
'vlogs': 'area/monitoring',
|
|
'vmstack': 'area/monitoring',
|
|
'grafana': 'area/monitoring',
|
|
'workloadmonitor': 'area/monitoring',
|
|
'monitoring-agents': 'area/monitoring',
|
|
|
|
// area/networking
|
|
'ingress': 'area/networking',
|
|
'ingress-nginx': 'area/networking',
|
|
'gateway': 'area/networking',
|
|
'vpn': 'area/networking',
|
|
'vpc': 'area/networking',
|
|
'metallb': 'area/networking',
|
|
'kube-ovn': 'area/networking',
|
|
'kubeovn-webhook': 'area/networking',
|
|
'tcp-balancer': 'area/networking',
|
|
'securitygroups': 'area/networking',
|
|
'cozy-proxy': 'area/networking',
|
|
'networking': 'area/networking',
|
|
'kubeovn': 'area/networking',
|
|
'multus': 'area/networking',
|
|
'kilo': 'area/networking',
|
|
'hetzner-robotlb': 'area/networking',
|
|
'ouroboros': 'area/networking',
|
|
|
|
// area/cilium
|
|
'cilium': 'area/cilium',
|
|
|
|
// area/openbao
|
|
'openbao': 'area/openbao',
|
|
'openbao-central': 'area/openbao',
|
|
|
|
// area/platform
|
|
'platform': 'area/platform',
|
|
'cert-manager': 'area/platform',
|
|
'external-secrets': 'area/platform',
|
|
'bundle': 'area/platform',
|
|
'flux': 'area/platform',
|
|
'fluxcd': 'area/platform',
|
|
'cluster-api': 'area/platform',
|
|
'talos': 'area/platform',
|
|
'installer': 'area/platform',
|
|
'cozyctl': 'area/platform',
|
|
'cozystack-engine': 'area/platform',
|
|
'cozy-lib': 'area/platform',
|
|
'operator': 'area/platform',
|
|
'cozystack-operator': 'area/platform',
|
|
'controller': 'area/platform',
|
|
'cozystack-controller': 'area/platform',
|
|
'cozystack-basics': 'area/platform',
|
|
'flux-shard-operator': 'area/platform',
|
|
'lineage': 'area/platform',
|
|
'migrations': 'area/platform',
|
|
'scheduler': 'area/platform',
|
|
'cozystack-scheduler': 'area/platform',
|
|
'reloader': 'area/platform',
|
|
'bootbox': 'area/platform',
|
|
|
|
// area/release
|
|
'backport': 'area/release',
|
|
'release': 'area/release',
|
|
'changelog': 'area/release',
|
|
'changelogs': 'area/release',
|
|
|
|
// area/storage
|
|
'seaweedfs': 'area/storage',
|
|
'seaweedfs-cosi-driver': 'area/storage',
|
|
'objectstorage-controller': 'area/storage',
|
|
'bucket': 'area/storage',
|
|
'linstor': 'area/storage',
|
|
'velero': 'area/storage',
|
|
'harbor': 'area/storage',
|
|
'backups': 'area/storage',
|
|
'csi': 'area/storage',
|
|
'storage': 'area/storage',
|
|
'piraeus-operator': 'area/storage',
|
|
'backupstrategy-controller': 'area/storage',
|
|
'local-storage': 'area/storage',
|
|
|
|
// area/tenant
|
|
'tenant': 'area/tenant',
|
|
|
|
// area/testing
|
|
'tests': 'area/testing',
|
|
'e2e': 'area/testing',
|
|
'testing': 'area/testing',
|
|
|
|
// area/virtualization
|
|
'kubevirt': 'area/virtualization',
|
|
'cdi': 'area/virtualization',
|
|
'vmi': 'area/virtualization',
|
|
'vm-import': 'area/virtualization',
|
|
'migration': 'area/virtualization',
|
|
'vm-instance': 'area/virtualization',
|
|
'vm-disk': 'area/virtualization',
|
|
'virtual-machine': 'area/virtualization',
|
|
'hami': 'area/virtualization',
|
|
'gpu-operator': 'area/virtualization',
|
|
'virtualization': 'area/virtualization',
|
|
'vm': 'area/virtualization',
|
|
'kubevirt-operator': 'area/virtualization',
|
|
'kubevirt-cdi': 'area/virtualization',
|
|
'kubevirt-instancetypes': 'area/virtualization',
|
|
};
|
|
|
|
const pr = context.payload.pull_request;
|
|
const title = pr.title || '';
|
|
const body = pr.body || '';
|
|
const existing = new Set((pr.labels || []).map(l => l.name));
|
|
const toAdd = new Set();
|
|
|
|
// 1. Strip every leading "[Backport release-1.x]" prefix: a backport of
|
|
// a backport carries two, and a leftover one would be parsed as the scope.
|
|
const cleanTitle = title.replace(/^(?:\[Backport [^\]]+\]\s+)+/, '');
|
|
if (cleanTitle !== title) {
|
|
toAdd.add('area/release');
|
|
}
|
|
|
|
// 2. Try Conventional Commits form: type(scope)?(!)?: description
|
|
const conv = cleanTitle.match(/^([a-z]+)(?:\(([^)]+)\))?(!)?:\s*.+$/);
|
|
// 3. Fall back to bracket form: [scope] description
|
|
const bracket = !conv && cleanTitle.match(/^\[([^\]]+)\]\s+.+$/);
|
|
|
|
let type = null, scopeStr = null, breaking = false;
|
|
if (conv) {
|
|
type = conv[1];
|
|
scopeStr = conv[2] || null;
|
|
breaking = !!conv[3];
|
|
} else if (bracket) {
|
|
scopeStr = bracket[1];
|
|
}
|
|
|
|
// 4. Detect BREAKING CHANGE: or BREAKING-CHANGE: footer in body.
|
|
// Conventional Commits 1.0 spec item 16 treats them as synonymous.
|
|
if (/^BREAKING[ -]CHANGE:/m.test(body)) {
|
|
breaking = true;
|
|
}
|
|
|
|
// 5. Apply kind/* from type.
|
|
if (type) {
|
|
// Object.hasOwn: a title typed or scoped `constructor` would otherwise
|
|
// resolve to an Object.prototype member instead of to nothing.
|
|
if (Object.hasOwn(typeToKind, type)) {
|
|
toAdd.add(typeToKind[type]);
|
|
} else {
|
|
core.warning(`type "${type}" has no kind/* mapping — typo or new type? See .github/workflows/pr-labeler.yaml typeToKind`);
|
|
}
|
|
}
|
|
|
|
// 6. Apply area/* from scope. Composite scopes split on comma.
|
|
const scopes = (scopeStr || '')
|
|
.split(/,\s*/)
|
|
.map(s => s.trim())
|
|
.filter(Boolean);
|
|
for (const s of scopes) {
|
|
if (Object.hasOwn(scopeToArea, s)) {
|
|
toAdd.add(scopeToArea[s]);
|
|
} else {
|
|
core.warning(`scope "${s}" has no area/* mapping — consider extending scopeToArea in .github/workflows/pr-labeler.yaml if it recurs`);
|
|
}
|
|
}
|
|
|
|
// 7. kind/breaking-change.
|
|
if (breaking) {
|
|
toAdd.add('kind/breaking-change');
|
|
}
|
|
|
|
// 8. Fallback: no area/* applied -> area/uncategorized.
|
|
const hasArea = [...toAdd].some(l => l.startsWith('area/'));
|
|
if (!hasArea) {
|
|
toAdd.add('area/uncategorized');
|
|
}
|
|
|
|
// 9. Additive only — never remove existing labels.
|
|
const newLabels = [...toAdd].filter(l => !existing.has(l));
|
|
if (newLabels.length === 0) {
|
|
core.info('No new labels to apply');
|
|
return;
|
|
}
|
|
|
|
await github.rest.issues.addLabels({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: pr.number,
|
|
labels: newLabels,
|
|
});
|
|
core.info(`Applied labels: ${newLabels.join(', ')}`);
|