mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-03 10:37:21 +09:00
The comment above the top-level `permissions` block claimed that jobs request the minimum extra scopes on top of it. A job-level block does not add to the workflow-level one: it replaces it, and every scope the job does not name is set to none. A job needing one extra scope has to restate `contents: read` alongside it or lose read access to the repository. The corrected wording states the replace semantics, so the next job block written against this comment grants the whole set it needs rather than only the delta. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la>
233 lines
9.2 KiB
YAML
233 lines
9.2 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',
|
|
|
|
// area/ai
|
|
'agents': 'area/ai',
|
|
'ai': 'area/ai',
|
|
|
|
// area/build
|
|
'build': 'area/build',
|
|
|
|
// area/ci
|
|
'ci': 'area/ci',
|
|
|
|
// area/dependencies — Renovate uses scope `deps` (see .github/renovate.json)
|
|
'deps': 'area/dependencies',
|
|
|
|
// area/dashboard
|
|
'dashboard': 'area/dashboard',
|
|
'cozystack-ui': 'area/dashboard',
|
|
'console': 'area/dashboard',
|
|
|
|
// area/database
|
|
'postgres': 'area/database',
|
|
'postgres-operator': 'area/database',
|
|
'mariadb': 'area/database',
|
|
'mariadb-operator': 'area/database',
|
|
'redis': 'area/database',
|
|
'etcd': 'area/database',
|
|
'kafka': 'area/database',
|
|
'clickhouse': 'area/database',
|
|
'mongodb': 'area/database',
|
|
|
|
// area/extra
|
|
'extra': 'area/extra',
|
|
|
|
// area/kamaji
|
|
'kamaji': 'area/kamaji',
|
|
|
|
// area/keycloak
|
|
'keycloak': 'area/keycloak',
|
|
|
|
// area/kubernetes
|
|
'kubernetes': 'area/kubernetes',
|
|
'apps/kubernetes': 'area/kubernetes',
|
|
|
|
// area/monitoring
|
|
'monitoring': 'area/monitoring',
|
|
'metrics-server': 'area/monitoring',
|
|
'vlogs': 'area/monitoring',
|
|
'vmstack': 'area/monitoring',
|
|
'grafana': 'area/monitoring',
|
|
'workloadmonitor': 'area/monitoring',
|
|
|
|
// area/networking
|
|
'ingress': 'area/networking',
|
|
'ingress-nginx': 'area/networking',
|
|
'gateway': 'area/networking',
|
|
'vpn': 'area/networking',
|
|
'metallb': 'area/networking',
|
|
'cilium': 'area/networking',
|
|
'kube-ovn': 'area/networking',
|
|
'tcp-balancer': 'area/networking',
|
|
'securitygroups': 'area/networking',
|
|
'cozy-proxy': 'area/networking',
|
|
|
|
// 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',
|
|
|
|
// area/release
|
|
'backport': 'area/release',
|
|
'release': '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',
|
|
|
|
// area/tenant
|
|
'tenant': 'area/tenant',
|
|
|
|
// area/testing
|
|
'tests': 'area/testing',
|
|
'e2e': 'area/testing',
|
|
|
|
// area/virtualization
|
|
'kubevirt': 'area/virtualization',
|
|
'cdi': 'area/virtualization',
|
|
'vmi': 'area/virtualization',
|
|
'vm-import': 'area/virtualization',
|
|
'virtual-machine': 'area/virtualization',
|
|
'hami': 'area/virtualization',
|
|
'gpu-operator': '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 "[Backport release-1.x]" prefix if present.
|
|
const backportMatch = title.match(/^\[Backport ([^\]]+)\]\s+(.+)$/);
|
|
const cleanTitle = backportMatch ? backportMatch[2] : title;
|
|
if (backportMatch) {
|
|
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) {
|
|
if (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 (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(', ')}`);
|